diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index 5c27da044e..d13e40248f 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -29,6 +29,7 @@ signout signup ssss + sygnal threepid unwedging diff --git a/CHANGES.md b/CHANGES.md index 82efc6d089..ff94b36770 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,8 @@ Improvements 🙌: - Drawer: move settings access and add sign out action (#2171) - Filter room member (and banned users) by name (#2184) - Implement "Jump to read receipt" and "Mention" actions on the room member profile screen + - Add Sygnal API implementation to test is Push are correctly received + - Add PushGateway API implementation to test if Push are correctly received Bugfix 🐛: - Improve support for image/audio/video/file selection with intent changes (#1376) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushGatewayFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushGatewayFailure.kt new file mode 100644 index 0000000000..b15147161f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushGatewayFailure.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.api.session.pushers + +import org.matrix.android.sdk.api.failure.Failure + +sealed class PushGatewayFailure : Failure.FeatureFailure() { + object PusherRejected : PushGatewayFailure() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 0e33be400c..3993422b1d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -66,6 +66,21 @@ interface PushersService { append: Boolean, withEventIdOnly: Boolean): UUID + /** + * Directly ask the push gateway to send a push to this device + * @param url the push gateway url (full path) + * @param appId the application id + * @param pushkey the FCM token + * @param eventId the eventId which will be sent in the Push message. Use a fake eventId. + * @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. + * In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid. + */ + fun testPush(url: String, + appId: String, + pushkey: String, + eventId: String, + callback: MatrixCallback): Cancelable + /** * Remove the http pusher */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt index 1154d1967c..a14c86efb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.network internal object NetworkConstants { - + // Homeserver private const val URI_API_PREFIX_PATH = "_matrix/client" const val URI_API_PREFIX_PATH_ = "$URI_API_PREFIX_PATH/" const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/" @@ -31,5 +31,9 @@ internal object NetworkConstants { const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2" const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/" + // Push Gateway + const val URI_PUSH_GATEWAY_PREFIX_PATH = "_matrix/push/v1/" + + // Integration const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/TLSSocketFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/TLSSocketFactory.kt index a5c711a3f0..c4dbce6240 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/TLSSocketFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/TLSSocketFactory.kt @@ -113,8 +113,4 @@ constructor(trustPinned: Array, acceptedTlsVersions: List): Cancelable { + return pushGatewayNotifyTask + .configureWith(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) { + this.callback = callback + } + .executeBy(taskExecutor) + } + override fun refreshPushers() { getPusherTask .configureWith() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index a6042b27a2..4030c63514 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -25,6 +25,8 @@ import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.internal.session.notification.DefaultProcessEventForPushTask import org.matrix.android.sdk.internal.session.notification.DefaultPushRuleService import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask +import org.matrix.android.sdk.internal.session.pushers.gateway.DefaultPushGatewayNotifyTask +import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask import org.matrix.android.sdk.internal.session.room.notification.DefaultSetRoomNotificationStateTask import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask import retrofit2.Retrofit @@ -86,4 +88,7 @@ internal abstract class PushersModule { @Binds abstract fun bindProcessEventForPushTask(task: DefaultProcessEventForPushTask): ProcessEventForPushTask + + @Binds + abstract fun bindPushGatewayNotifyTask(task: DefaultPushGatewayNotifyTask): PushGatewayNotifyTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt new file mode 100644 index 0000000000..d95587fc22 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.pushers.gateway + +import org.matrix.android.sdk.internal.network.NetworkConstants +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST + +internal interface PushGatewayAPI { + /** + * Ask the Push Gateway to send a push to the current device. + * + * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#post-matrix-push-v1-notify + */ + @POST(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH + "notify") + fun notify(@Body body: PushGatewayNotifyBody): Call +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayDevice.kt new file mode 100644 index 0000000000..5490fed7a7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayDevice.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.pushers.gateway + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class PushGatewayDevice( + /** + * Required. The app_id given when the pusher was created. + */ + @Json(name = "app_id") + val appId: String, + /** + * Required. The pushkey given when the pusher was created. + */ + @Json(name = "pushkey") + val pushKey: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotification.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotification.kt new file mode 100644 index 0000000000..b9c8a35afc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotification.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.pushers.gateway + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class PushGatewayNotification( + @Json(name = "event_id") + val eventId: String, + + /** + * Required. This is an array of devices that the notification should be sent to. + */ + @Json(name = "devices") + val devices: List +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyBody.kt new file mode 100644 index 0000000000..751414d2fe --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyBody.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.pushers.gateway + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class PushGatewayNotifyBody( + /** + * Required. Information about the push notification + */ + @Json(name = "notification") + val notification: PushGatewayNotification +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyResponse.kt new file mode 100644 index 0000000000..50401ef6e9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyResponse.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.pushers.gateway + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class PushGatewayNotifyResponse( + @Json(name = "rejected") + val rejectedPushKeys: List +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt new file mode 100644 index 0000000000..df6f46fa81 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 org.matrix.android.sdk.internal.session.pushers.gateway + +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure +import org.matrix.android.sdk.internal.di.Unauthenticated +import org.matrix.android.sdk.internal.network.NetworkConstants +import org.matrix.android.sdk.internal.network.RetrofitFactory +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface PushGatewayNotifyTask : Task { + data class Params( + val url: String, + val appId: String, + val pushKey: String, + val eventId: String + ) +} + +internal class DefaultPushGatewayNotifyTask @Inject constructor( + private val retrofitFactory: RetrofitFactory, + @Unauthenticated private val unauthenticatedOkHttpClient: OkHttpClient +) : PushGatewayNotifyTask { + + override suspend fun execute(params: PushGatewayNotifyTask.Params) { + val sygnalApi = retrofitFactory.create( + unauthenticatedOkHttpClient, + params.url.substringBefore(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH) + ) + .create(PushGatewayAPI::class.java) + + val response = executeRequest(null) { + apiCall = sygnalApi.notify( + PushGatewayNotifyBody( + PushGatewayNotification( + eventId = params.eventId, + devices = listOf( + PushGatewayDevice( + params.appId, + params.pushKey + ) + ) + ) + ) + ) + } + + if (response.rejectedPushKeys.contains(params.pushKey)) { + throw PushGatewayFailure.PusherRejected + } + } +} diff --git a/tools/tests/test_push.sh b/tools/tests/test_push.sh new file mode 100755 index 0000000000..08348497ae --- /dev/null +++ b/tools/tests/test_push.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# Some doc +# https://firebase.google.com/docs/cloud-messaging/android/first-message +# http://bulkpush.com/pushnotification/guidedetail/s-4/android-gcm-api-configuration +# http://www.feelzdroid.com/2016/02/android-google-cloud-messaging-push-notifications-gcm-tutorial.html + +# Ask for parameter +read -p "Enter the server API key: " SERVER_KEY + +echo +echo "Check validity of API key, InvalidRegistration error is OK" +# https://developers.google.com/cloud-messaging/http + +curl -H "Authorization: key=$SERVER_KEY" \ + -H Content-Type:"application/json" \ + -d "{\"registration_ids\":[\"ABC\"]}" \ + -s \ + https://fcm.googleapis.com/fcm/send \ + | python -m json.tool + +# should obtain something like this: +# {"multicast_id":5978845027639121780,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]} + +read -p "Enter the FCM token: " FCM_TOKEN + +# content of the notification +DATA='{"event_id":"$THIS_IS_A_FAKE_EVENT_ID"}' + +echo +echo +echo "Send a push, you should see success:1..." + +curl -H "Authorization: key=$SERVER_KEY" \ + -H Content-Type:"application/json" \ + -d "{ \"data\" : $DATA, \"to\":\"$FCM_TOKEN\" }" \ + -s \ + https://fcm.googleapis.com/fcm/send \ + | python -m json.tool + +echo +echo + +# should obtain something like this: +# {"multicast_id":7967233883611790812,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1472636210339069%84ac25d9f9fd7ecd"}]} + diff --git a/tools/tests/test_push_unsafe.sh b/tools/tests/test_push_unsafe.sh new file mode 100755 index 0000000000..77c198197f --- /dev/null +++ b/tools/tests/test_push_unsafe.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# Copy and adaptation of ./test_push.sh, which takes 2 params: server key and fcm token. +# It's unsafe to use it because it takes server key as parameter, that will remain in the terminal history. + +# Some doc +# https://firebase.google.com/docs/cloud-messaging/android/first-message +# http://bulkpush.com/pushnotification/guidedetail/s-4/android-gcm-api-configuration +# http://www.feelzdroid.com/2016/02/android-google-cloud-messaging-push-notifications-gcm-tutorial.html + +if [[ "$#" -ne 2 ]]; then + echo "Usage: $0 SERVER_KEY FCM_TOKEN" >&2 + exit 1 +fi + +# Get the command line parameters +SERVER_KEY=$1 +FCM_TOKEN=$2 + +echo +echo "Check validity of API key, InvalidRegistration error is OK" +# https://developers.google.com/cloud-messaging/http + +curl -H "Authorization: key=$SERVER_KEY" \ + -H Content-Type:"application/json" \ + -d "{\"registration_ids\":[\"ABC\"]}" \ + -s \ + https://fcm.googleapis.com/fcm/send \ + | python -m json.tool + +# should obtain something like this: +# {"multicast_id":5978845027639121780,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]} + +# content of the notification +DATA='{"event_id":"$THIS_IS_A_FAKE_EVENT_ID"}' + +echo +echo +echo "Send a push, you should see success:1..." + +curl -H "Authorization: key=$SERVER_KEY" \ + -H Content-Type:"application/json" \ + -d "{ \"data\" : $DATA, \"to\":\"$FCM_TOKEN\" }" \ + -s \ + https://fcm.googleapis.com/fcm/send \ + | python -m json.tool + +echo +echo + +# should obtain something like this: +# {"multicast_id":7967233883611790812,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1472636210339069%84ac25d9f9fd7ecd"}]} + diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt index 65b8609446..6236aad65c 100644 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -22,17 +22,21 @@ import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimizati import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestDeviceSettings +import im.vector.app.features.settings.troubleshoot.TestNotification import im.vector.app.features.settings.troubleshoot.TestPushRulesSettings import im.vector.app.features.settings.troubleshoot.TestSystemSettings import javax.inject.Inject -class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings, - private val testAccountSettings: TestAccountSettings, - private val testDeviceSettings: TestDeviceSettings, - private val testPushRulesSettings: TestPushRulesSettings, - private val testAutoStartBoot: TestAutoStartBoot, - private val testBackgroundRestrictions: TestBackgroundRestrictions, - private val testBatteryOptimization: TestBatteryOptimization) { +class NotificationTroubleshootTestManagerFactory @Inject constructor( + private val testSystemSettings: TestSystemSettings, + private val testAccountSettings: TestAccountSettings, + private val testDeviceSettings: TestDeviceSettings, + private val testPushRulesSettings: TestPushRulesSettings, + private val testAutoStartBoot: TestAutoStartBoot, + private val testBackgroundRestrictions: TestBackgroundRestrictions, + private val testBatteryOptimization: TestBatteryOptimization, + private val testNotification: TestNotification +) { fun create(fragment: Fragment): NotificationTroubleshootTestManager { val mgr = NotificationTroubleshootTestManager(fragment) @@ -43,6 +47,7 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val mgr.addTest(testAutoStartBoot) mgr.addTest(testBackgroundRestrictions) mgr.addTest(testBatteryOptimization) + mgr.addTest(testNotification) return mgr } } diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt new file mode 100644 index 0000000000..da93d54075 --- /dev/null +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2020 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.app.gplay.features.settings.troubleshoot + +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher +import androidx.appcompat.app.AppCompatActivity +import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.settings.troubleshoot.TroubleshootTest +import im.vector.app.push.fcm.FcmHelper +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure +import org.matrix.android.sdk.api.util.Cancelable +import javax.inject.Inject + +/** + * Test Push by asking the Push Gateway to send a Push back + */ +class TestPushFromPushGateway @Inject constructor(private val context: AppCompatActivity, + private val stringProvider: StringProvider, + private val errorFormatter: ErrorFormatter, + private val pushersManager: PushersManager) + : TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) { + + private var action: Cancelable? = null + + override fun perform(activityResultLauncher: ActivityResultLauncher) { + val fcmToken = FcmHelper.getFcmToken(context) ?: run { + status = TestStatus.FAILED + return + } + action = pushersManager.testPush(fcmToken, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + description = if (failure is PushGatewayFailure.PusherRejected) { + stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed) + } else { + errorFormatter.toHumanReadable(failure) + } + status = TestStatus.FAILED + } + + override fun onSuccess(data: Unit) { + // Wait for the push to be received + description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) + status = TestStatus.RUNNING + } + }) + } + + override fun onPushReceived() { + description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_success) + status = TestStatus.SUCCESS + } + + override fun cancel() { + action?.cancel() + } +} diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index 8fdb65c8d0..cfd241d4f9 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -19,10 +19,12 @@ package im.vector.app.gplay.push.fcm +import android.content.Intent import android.os.Handler import android.os.Looper import androidx.lifecycle.Lifecycle import androidx.lifecycle.ProcessLifecycleOwner +import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import im.vector.app.BuildConfig @@ -34,6 +36,7 @@ import im.vector.app.features.badge.BadgeProxy import im.vector.app.features.notifications.NotifiableEventResolver import im.vector.app.features.notifications.NotifiableMessageEvent import im.vector.app.features.notifications.NotificationDrawerManager +import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.SimpleNotifiableEvent import im.vector.app.features.settings.VectorPreferences import im.vector.app.push.fcm.FcmHelper @@ -60,11 +63,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { override fun onCreate() { super.onCreate() - notificationDrawerManager = vectorComponent().notificationDrawerManager() - notifiableEventResolver = vectorComponent().notifiableEventResolver() - pusherManager = vectorComponent().pusherManager() - activeSessionHolder = vectorComponent().activeSessionHolder() - vectorPreferences = vectorComponent().vectorPreferences() + with(vectorComponent()) { + notificationDrawerManager = notificationDrawerManager() + notifiableEventResolver = notifiableEventResolver() + pusherManager = pusherManager() + activeSessionHolder = activeSessionHolder() + vectorPreferences = vectorPreferences() + } } /** @@ -73,6 +78,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * @param message the message */ override fun onMessageReceived(message: RemoteMessage) { + // Diagnostic Push + if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) { + val intent = Intent(NotificationUtils.PUSH_ACTION) + LocalBroadcastManager.getInstance(this).sendBroadcast(intent) + return + } + if (!vectorPreferences.areNotificationEnabledForDevice()) { Timber.i("Notification are disabled for this device") return diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt index b2dad09483..e96c603e60 100644 --- a/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -19,20 +19,26 @@ import androidx.fragment.app.Fragment import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestDeviceSettings +import im.vector.app.features.settings.troubleshoot.TestNotification import im.vector.app.features.settings.troubleshoot.TestPushRulesSettings import im.vector.app.features.settings.troubleshoot.TestSystemSettings import im.vector.app.gplay.features.settings.troubleshoot.TestFirebaseToken import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices +import im.vector.app.gplay.features.settings.troubleshoot.TestPushFromPushGateway import im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration import javax.inject.Inject -class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings, - private val testAccountSettings: TestAccountSettings, - private val testDeviceSettings: TestDeviceSettings, - private val testBingRulesSettings: TestPushRulesSettings, - private val testPlayServices: TestPlayServices, - private val testFirebaseToken: TestFirebaseToken, - private val testTokenRegistration: TestTokenRegistration) { +class NotificationTroubleshootTestManagerFactory @Inject constructor( + private val testSystemSettings: TestSystemSettings, + private val testAccountSettings: TestAccountSettings, + private val testDeviceSettings: TestDeviceSettings, + private val testBingRulesSettings: TestPushRulesSettings, + private val testPlayServices: TestPlayServices, + private val testFirebaseToken: TestFirebaseToken, + private val testTokenRegistration: TestTokenRegistration, + private val testPushFromPushGateway: TestPushFromPushGateway, + private val testNotification: TestNotification +) { fun create(fragment: Fragment): NotificationTroubleshootTestManager { val mgr = NotificationTroubleshootTestManager(fragment) @@ -43,6 +49,8 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val mgr.addTest(testPlayServices) mgr.addTest(testFirebaseToken) mgr.addTest(testTokenRegistration) + mgr.addTest(testPushFromPushGateway) + mgr.addTest(testNotification) return mgr } } diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index bc5577b467..23e483f50b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -254,6 +254,10 @@ android:name=".features.call.service.CallHeadsUpActionReceiver" android:exported="false" /> + + stringProvider.getString(R.string.error_network_timeout) - is UnknownHostException -> - // Invalid homeserver? - // TODO Check network state, airplane mode, etc. - stringProvider.getString(R.string.login_error_unknown_host) is SSLPeerUnverifiedException -> stringProvider.getString(R.string.login_error_ssl_peer_unverified) is SSLException -> stringProvider.getString(R.string.login_error_ssl_other) else -> + // TODO Check network state, airplane mode, etc. stringProvider.getString(R.string.error_no_network) } } diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index f0d27182e7..5fe30141d9 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -22,6 +22,7 @@ import im.vector.app.core.resources.AppNameProvider import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.StringProvider import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.util.Cancelable import java.util.UUID import javax.inject.Inject import kotlin.math.abs @@ -34,6 +35,17 @@ class PushersManager @Inject constructor( private val stringProvider: StringProvider, private val appNameProvider: AppNameProvider ) { + fun testPush(pushKey: String, callback: MatrixCallback): Cancelable { + val currentSession = activeSessionHolder.getActiveSession() + + return currentSession.testPush( + stringProvider.getString(R.string.pusher_http_url), + stringProvider.getString(R.string.pusher_app_id), + pushKey, + TEST_EVENT_ID, + callback + ) + } fun registerPusherWithFcmKey(pushKey: String): UUID { val currentSession = activeSessionHolder.getActiveSession() @@ -56,4 +68,8 @@ class PushersManager @Inject constructor( val currentSession = activeSessionHolder.getSafeActiveSession() ?: return currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id), callback) } + + companion object { + const val TEST_EVENT_ID = "\$THIS_IS_A_FAKE_EVENT_ID" + } } diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt index 5e722ae209..d228adab12 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt @@ -30,7 +30,6 @@ import android.provider.Settings import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.annotation.StringRes -import androidx.appcompat.app.AppCompatActivity import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import im.vector.app.R @@ -97,15 +96,15 @@ fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = t * Shows notification settings for the current app. * In android O will directly opens the notification settings, in lower version it will show the App settings */ -fun startNotificationSettingsIntent(activity: AppCompatActivity, activityResultLauncher: ActivityResultLauncher) { +fun startNotificationSettingsIntent(context: Context, activityResultLauncher: ActivityResultLauncher) { val intent = Intent() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS - intent.putExtra(Settings.EXTRA_APP_PACKAGE, activity.packageName) + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) } else { intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS - intent.putExtra("app_package", activity.packageName) - intent.putExtra("app_uid", activity.applicationInfo?.uid) + intent.putExtra("app_package", context.packageName) + intent.putExtra("app_uid", context.applicationInfo?.uid) } activityResultLauncher.launch(intent) } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt index 4b9f528254..af959fecd4 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginServerUrlFormFragment.kt @@ -28,6 +28,8 @@ import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.openUrlInChromeCustomTab import kotlinx.android.synthetic.main.fragment_login_server_url_form.* +import org.matrix.android.sdk.api.failure.Failure +import java.net.UnknownHostException import javax.inject.Inject /** @@ -115,7 +117,13 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment() } override fun onError(throwable: Throwable) { - loginServerUrlFormHomeServerUrlTil.error = errorFormatter.toHumanReadable(throwable) + loginServerUrlFormHomeServerUrlTil.error = if (throwable is Failure.NetworkConnection + && throwable.ioException is UnknownHostException) { + // Invalid homeserver? + getString(R.string.login_error_homeserver_not_found) + } else { + errorFormatter.toHumanReadable(throwable) + } } override fun updateWithState(state: LoginViewState) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 26f00fcef9..7147968d49 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -590,6 +590,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } } + fun displayDiagnosticNotification() { + notificationUtils.displayDiagnosticNotification() + } + companion object { private const val SUMMARY_NOTIFICATION_ID = 0 private const val ROOM_MESSAGES_NOTIFICATION_ID = 1 diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 9d89168bb8..44eb278c64 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -27,8 +27,10 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.graphics.Bitmap +import android.graphics.Canvas import android.net.Uri import android.os.Build +import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -36,6 +38,7 @@ import androidx.core.app.RemoteInput import androidx.core.app.TaskStackBuilder import androidx.core.content.ContextCompat import androidx.core.content.getSystemService +import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.IconCompat import androidx.fragment.app.Fragment import im.vector.app.BuildConfig @@ -47,8 +50,8 @@ import im.vector.app.features.call.service.CallHeadsUpActionReceiver import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs -import im.vector.app.features.pin.PinLocker import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -61,7 +64,6 @@ import kotlin.random.Random @Singleton class NotificationUtils @Inject constructor(private val context: Context, private val stringProvider: StringProvider, - private val pinLocker: PinLocker, private val vectorPreferences: VectorPreferences) { companion object { @@ -89,6 +91,8 @@ class NotificationUtils @Inject constructor(private val context: Context, const val DISMISS_SUMMARY_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DISMISS_SUMMARY_ACTION" const val DISMISS_ROOM_NOTIF_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DISMISS_ROOM_NOTIF_ACTION" private const val TAP_TO_VIEW_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.TAP_TO_VIEW_ACTION" + const val DIAGNOSTIC_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DIAGNOSTIC" + const val PUSH_ACTION = "${BuildConfig.APPLICATION_ID}.PUSH" /* ========================================================================================== * IDs for channels @@ -845,6 +849,43 @@ class NotificationUtils @Inject constructor(private val context: Context, } } + fun displayDiagnosticNotification() { + val testActionIntent = Intent(context, TestNotificationReceiver::class.java) + testActionIntent.action = DIAGNOSTIC_ACTION + val testPendingIntent = PendingIntent.getBroadcast( + context, + 0, + testActionIntent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + + notificationManager.notify( + "DIAGNOSTIC", + 888, + NotificationCompat.Builder(context, NOISY_NOTIFICATION_CHANNEL_ID) + .setContentTitle(stringProvider.getString(R.string.app_name)) + .setContentText(stringProvider.getString(R.string.settings_troubleshoot_test_push_notification_content)) + .setSmallIcon(R.drawable.ic_status_bar) + .setLargeIcon(getBitmap(context, R.drawable.element_logo_green)) + .setColor(ContextCompat.getColor(context, R.color.notification_accent_color)) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setCategory(NotificationCompat.CATEGORY_STATUS) + .setAutoCancel(true) + .setContentIntent(testPendingIntent) + .build() + ) + } + + private fun getBitmap(context: Context, @DrawableRes drawableRes: Int): Bitmap? { + val drawable = ResourcesCompat.getDrawable(context.resources, drawableRes, null) ?: return null + val canvas = Canvas() + val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) + canvas.setBitmap(bitmap) + drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) + drawable.draw(canvas) + return bitmap + } + /** * Return true it the user has enabled the do not disturb mode */ diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt index b989ae44d0..9dc6dc1751 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt @@ -16,12 +16,16 @@ package im.vector.app.features.settings import android.app.Activity +import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.os.Bundle import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.TextView +import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -32,10 +36,13 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TroubleshootTest import im.vector.app.push.fcm.NotificationTroubleshootTestManagerFactory +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( @@ -45,12 +52,16 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( @BindView(R.id.troubleshoot_test_recycler_view) lateinit var mRecyclerView: RecyclerView + @BindView(R.id.troubleshoot_bottom_view) lateinit var mBottomView: ViewGroup + @BindView(R.id.toubleshoot_summ_description) lateinit var mSummaryDescription: TextView + @BindView(R.id.troubleshoot_summ_button) lateinit var mSummaryButton: Button + @BindView(R.id.troubleshoot_run_button) lateinit var mRunButton: Button @@ -82,8 +93,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( } private fun startUI() { - mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, - 0, 0) + mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, 0, 0) testManager = testManagerFactory.create(this) testManager?.statusListener = { troubleshootTestManager -> if (isAdded) { @@ -94,10 +104,10 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( mSummaryButton.visibility = View.GONE mRunButton.visibility = View.VISIBLE } - TroubleshootTest.TestStatus.RUNNING -> { - // Forces int type because it's breaking lint - val size: Int = troubleshootTestManager.testList.size - val currentTestIndex: Int = troubleshootTestManager.currentTestIndex + TroubleshootTest.TestStatus.RUNNING, + TroubleshootTest.TestStatus.WAITING_FOR_USER -> { + val size = troubleshootTestManager.testListSize + val currentTestIndex = troubleshootTestManager.currentTestIndex mSummaryDescription.text = getString( R.string.settings_troubleshoot_diagnostic_running_status, currentTestIndex, @@ -108,15 +118,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( } TroubleshootTest.TestStatus.FAILED -> { // check if there are quick fixes - var hasQuickFix = false - testManager?.testList?.let { - for (test in it) { - if (test.status == TroubleshootTest.TestStatus.FAILED && test.quickFix != null) { - hasQuickFix = true - break - } - } - } + val hasQuickFix = testManager?.hasQuickFix().orFalse() if (hasQuickFix) { mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_with_quickfix) } else { @@ -161,6 +163,39 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( override fun onResume() { super.onResume() (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notification_troubleshoot) + + tryOrNull("Unable to register the receiver") { + LocalBroadcastManager.getInstance(requireContext()) + .registerReceiver(broadcastReceiverPush, IntentFilter(NotificationUtils.PUSH_ACTION)) + } + tryOrNull("Unable to register the receiver") { + LocalBroadcastManager.getInstance(requireContext()) + .registerReceiver(broadcastReceiverNotification, IntentFilter(NotificationUtils.DIAGNOSTIC_ACTION)) + } + } + + override fun onPause() { + super.onPause() + tryOrNull { + LocalBroadcastManager.getInstance(requireContext()) + .unregisterReceiver(broadcastReceiverPush) + } + tryOrNull { + LocalBroadcastManager.getInstance(requireContext()) + .unregisterReceiver(broadcastReceiverNotification) + } + } + + private val broadcastReceiverPush = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + testManager?.onDiagnosticPushReceived() + } + } + + private val broadcastReceiverNotification = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + testManager?.onDiagnosticNotificationClicked() + } } override fun onAttach(context: Context) { diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt index 1ccd7b6735..633d9d05fe 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootRecyclerViewAdapter.kt @@ -77,6 +77,16 @@ class NotificationTroubleshootRecyclerViewAdapter(val tests: ArrayList { + progressBar.visibility = View.INVISIBLE + statusIconImage.visibility = View.VISIBLE + val infoColor = ContextCompat.getColor(context, R.color.vector_info_color) + val drawable = ContextCompat.getDrawable(itemView.context, R.drawable.ic_notification_privacy_warning)?.apply { + ThemeUtils.tintDrawableWithColor(this, infoColor) + } + statusIconImage.setImageDrawable(drawable) + descriptionText.setTextColor(infoColor) + } TroubleshootTest.TestStatus.RUNNING -> { progressBar.visibility = View.VISIBLE statusIconImage.visibility = View.INVISIBLE diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootTestManager.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootTestManager.kt index e977dc5963..7e7ca57243 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootTestManager.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/NotificationTroubleshootTestManager.kt @@ -23,13 +23,19 @@ import androidx.fragment.app.Fragment import kotlin.properties.Delegates class NotificationTroubleshootTestManager(val fragment: Fragment) { + private val testList = ArrayList() + + val testListSize: Int + get() = testList.size - val testList = ArrayList() var isCancelled = false + private set var currentTestIndex by Delegates.observable(0) { _, _, _ -> statusListener?.invoke(this) } + private set + val adapter = NotificationTroubleshootRecyclerViewAdapter(testList) var statusListener: ((NotificationTroubleshootTestManager) -> Unit)? = null @@ -37,6 +43,7 @@ class NotificationTroubleshootTestManager(val fragment: Fragment) { var diagStatus: TroubleshootTest.TestStatus by Delegates.observable(TroubleshootTest.TestStatus.NOT_STARTED) { _, _, _ -> statusListener?.invoke(this) } + private set fun addTest(test: TroubleshootTest) { testList.add(test) @@ -79,19 +86,31 @@ class NotificationTroubleshootTestManager(val fragment: Fragment) { } fun retry(activityResultLauncher: ActivityResultLauncher) { - for (test in testList) { - test.cancel() - test.description = null - test.quickFix = null - test.status = TroubleshootTest.TestStatus.NOT_STARTED + testList.forEach { + it.cancel() + it.description = null + it.quickFix = null + it.status = TroubleshootTest.TestStatus.NOT_STARTED } runDiagnostic(activityResultLauncher) } - fun cancel() { - isCancelled = true - for (test in testList) { - test.cancel() + fun hasQuickFix(): Boolean { + return testList.any { test -> + test.status == TroubleshootTest.TestStatus.FAILED && test.quickFix != null } } + + fun cancel() { + isCancelled = true + testList.forEach { it.cancel() } + } + + fun onDiagnosticPushReceived() { + testList.forEach { it.onPushReceived() } + } + + fun onDiagnosticNotificationClicked() { + testList.forEach { it.onNotificationClicked() } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt new file mode 100644 index 0000000000..6f25ecfe39 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotification.kt @@ -0,0 +1,54 @@ +/* + * 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.app.features.settings.troubleshoot + +import android.content.Context +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.startNotificationSettingsIntent +import im.vector.app.features.notifications.NotificationUtils +import javax.inject.Inject + +/** + * Checks if notifications can be displayed and clicked by the user + */ +class TestNotification @Inject constructor(private val context: Context, + private val notificationUtils: NotificationUtils, + private val stringProvider: StringProvider) + : TroubleshootTest(R.string.settings_troubleshoot_test_notification_title) { + + override fun perform(activityResultLauncher: ActivityResultLauncher) { + // Display the notification right now + notificationUtils.displayDiagnosticNotification() + description = stringProvider.getString(R.string.settings_troubleshoot_test_notification_notice) + + quickFix = object : TroubleshootQuickFix(R.string.open_settings) { + override fun doFix() { + startNotificationSettingsIntent(context, activityResultLauncher) + } + } + + status = TestStatus.WAITING_FOR_USER + } + + override fun onNotificationClicked() { + description = stringProvider.getString(R.string.settings_troubleshoot_test_notification_notification_clicked) + quickFix = null + status = TestStatus.SUCCESS + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotificationReceiver.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotificationReceiver.kt new file mode 100644 index 0000000000..7dec870d3d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestNotificationReceiver.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 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.app.features.settings.troubleshoot + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.localbroadcastmanager.content.LocalBroadcastManager + +class TestNotificationReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + // Internal broadcast to any one interested + LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt index 8e64514e7f..ee652288be 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestSystemSettings.kt @@ -15,9 +15,9 @@ */ package im.vector.app.features.settings.troubleshoot +import android.content.Context import android.content.Intent import androidx.activity.result.ActivityResultLauncher -import androidx.appcompat.app.AppCompatActivity import androidx.core.app.NotificationManagerCompat import im.vector.app.R import im.vector.app.core.resources.StringProvider @@ -27,7 +27,7 @@ import javax.inject.Inject /** * Checks if notifications are enable in the system settings for this app. */ -class TestSystemSettings @Inject constructor(private val context: AppCompatActivity, +class TestSystemSettings @Inject constructor(private val context: Context, private val stringProvider: StringProvider) : TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) { diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TroubleshootTest.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TroubleshootTest.kt index f894fcc0ef..76ba2378a0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TroubleshootTest.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TroubleshootTest.kt @@ -25,6 +25,7 @@ abstract class TroubleshootTest(@StringRes val titleResId: Int) { enum class TestStatus { NOT_STARTED, RUNNING, + WAITING_FOR_USER, FAILED, SUCCESS } @@ -51,4 +52,10 @@ abstract class TroubleshootTest(@StringRes val titleResId: Int) { open fun cancel() { } + + open fun onPushReceived() { + } + + open fun onNotificationClicked() { + } } diff --git a/vector/src/main/res/layout/activity_jitsi.xml b/vector/src/main/res/layout/activity_jitsi.xml index e0359d220d..de0c0271bc 100644 --- a/vector/src/main/res/layout/activity_jitsi.xml +++ b/vector/src/main/res/layout/activity_jitsi.xml @@ -1,11 +1,12 @@ - + + android:orientation="vertical" + tools:ignore="UselessParent"> - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d48162de29..e400f7fe64 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -749,6 +749,15 @@ FCM token successfully registered to HomeServer. Failed to register FCM token to HomeServer:\n%1$s + Test Push + The application is waiting for the PUSH + The application is receiving PUSH + Failed to receive push. Solution could be to reinstall the application. + You are viewing the notification! Click me! + Notification Display + Please click on the notification. If you do not see the notification, please check the system settings. + The notification has been clicked! + Notifications Service Notifications Service is running. Notifications Service is not running.\nTry to restart the application.