Merge pull request #2203 from vector-im/feature/bma/pusher_data

Feature/bma/pusher data
This commit is contained in:
Benoit Marty 2020-10-12 14:51:15 +02:00 committed by GitHub
commit 6f709a1e7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 778 additions and 134 deletions

View File

@ -29,6 +29,7 @@
<w>signout</w> <w>signout</w>
<w>signup</w> <w>signup</w>
<w>ssss</w> <w>ssss</w>
<w>sygnal</w>
<w>threepid</w> <w>threepid</w>
<w>unwedging</w> <w>unwedging</w>
</words> </words>

View File

@ -16,6 +16,8 @@ Improvements 🙌:
- Drawer: move settings access and add sign out action (#2171) - Drawer: move settings access and add sign out action (#2171)
- Filter room member (and banned users) by name (#2184) - Filter room member (and banned users) by name (#2184)
- Implement "Jump to read receipt" and "Mention" actions on the room member profile screen - 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 🐛: Bugfix 🐛:
- Improve support for image/audio/video/file selection with intent changes (#1376) - Improve support for image/audio/video/file selection with intent changes (#1376)

View File

@ -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()
}

View File

@ -66,6 +66,21 @@ interface PushersService {
append: Boolean, append: Boolean,
withEventIdOnly: Boolean): UUID 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<Unit>): Cancelable
/** /**
* Remove the http pusher * Remove the http pusher
*/ */

View File

@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.network package org.matrix.android.sdk.internal.network
internal object NetworkConstants { internal object NetworkConstants {
// Homeserver
private const val URI_API_PREFIX_PATH = "_matrix/client" 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_ = "$URI_API_PREFIX_PATH/"
const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/" 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_PREFIX_PATH = "_matrix/identity/v2"
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/" 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/" const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
} }

View File

@ -113,8 +113,4 @@ constructor(trustPinned: Array<TrustManager>, acceptedTlsVersions: List<TlsVersi
} }
return socket return socket
} }
companion object {
private val LOG_TAG = TLSSocketFactory::class.java.simpleName
}
} }

View File

@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
@ -41,10 +42,23 @@ internal class DefaultPushersService @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
@SessionId private val sessionId: String, @SessionId private val sessionId: String,
private val getPusherTask: GetPushersTask, private val getPusherTask: GetPushersTask,
private val pushGatewayNotifyTask: PushGatewayNotifyTask,
private val removePusherTask: RemovePusherTask, private val removePusherTask: RemovePusherTask,
private val taskExecutor: TaskExecutor private val taskExecutor: TaskExecutor
) : PushersService { ) : PushersService {
override fun testPush(url: String,
appId: String,
pushkey: String,
eventId: String,
callback: MatrixCallback<Unit>): Cancelable {
return pushGatewayNotifyTask
.configureWith(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun refreshPushers() { override fun refreshPushers() {
getPusherTask getPusherTask
.configureWith() .configureWith()

View File

@ -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.DefaultProcessEventForPushTask
import org.matrix.android.sdk.internal.session.notification.DefaultPushRuleService 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.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.DefaultSetRoomNotificationStateTask
import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask
import retrofit2.Retrofit import retrofit2.Retrofit
@ -86,4 +88,7 @@ internal abstract class PushersModule {
@Binds @Binds
abstract fun bindProcessEventForPushTask(task: DefaultProcessEventForPushTask): ProcessEventForPushTask abstract fun bindProcessEventForPushTask(task: DefaultProcessEventForPushTask): ProcessEventForPushTask
@Binds
abstract fun bindPushGatewayNotifyTask(task: DefaultPushGatewayNotifyTask): PushGatewayNotifyTask
} }

View File

@ -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<PushGatewayNotifyResponse>
}

View File

@ -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
)

View File

@ -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<PushGatewayDevice>
)

View File

@ -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
)

View File

@ -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<String>
)

View File

@ -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<PushGatewayNotifyTask.Params, Unit> {
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<PushGatewayNotifyResponse>(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
}
}
}

46
tools/tests/test_push.sh Executable file
View File

@ -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"}]}

53
tools/tests/test_push_unsafe.sh Executable file
View File

@ -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"}]}

View File

@ -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.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestAccountSettings
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings 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.TestPushRulesSettings
import im.vector.app.features.settings.troubleshoot.TestSystemSettings import im.vector.app.features.settings.troubleshoot.TestSystemSettings
import javax.inject.Inject import javax.inject.Inject
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings, class NotificationTroubleshootTestManagerFactory @Inject constructor(
private val testAccountSettings: TestAccountSettings, private val testSystemSettings: TestSystemSettings,
private val testDeviceSettings: TestDeviceSettings, private val testAccountSettings: TestAccountSettings,
private val testPushRulesSettings: TestPushRulesSettings, private val testDeviceSettings: TestDeviceSettings,
private val testAutoStartBoot: TestAutoStartBoot, private val testPushRulesSettings: TestPushRulesSettings,
private val testBackgroundRestrictions: TestBackgroundRestrictions, private val testAutoStartBoot: TestAutoStartBoot,
private val testBatteryOptimization: TestBatteryOptimization) { private val testBackgroundRestrictions: TestBackgroundRestrictions,
private val testBatteryOptimization: TestBatteryOptimization,
private val testNotification: TestNotification
) {
fun create(fragment: Fragment): NotificationTroubleshootTestManager { fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment) val mgr = NotificationTroubleshootTestManager(fragment)
@ -43,6 +47,7 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
mgr.addTest(testAutoStartBoot) mgr.addTest(testAutoStartBoot)
mgr.addTest(testBackgroundRestrictions) mgr.addTest(testBackgroundRestrictions)
mgr.addTest(testBatteryOptimization) mgr.addTest(testBatteryOptimization)
mgr.addTest(testNotification)
return mgr return mgr
} }
} }

View File

@ -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<Intent>) {
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
status = TestStatus.FAILED
return
}
action = pushersManager.testPush(fcmToken, object : MatrixCallback<Unit> {
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()
}
}

View File

@ -19,10 +19,12 @@
package im.vector.app.gplay.push.fcm package im.vector.app.gplay.push.fcm
import android.content.Intent
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import im.vector.app.BuildConfig 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.NotifiableEventResolver
import im.vector.app.features.notifications.NotifiableMessageEvent import im.vector.app.features.notifications.NotifiableMessageEvent
import im.vector.app.features.notifications.NotificationDrawerManager 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.notifications.SimpleNotifiableEvent
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
@ -60,11 +63,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
notificationDrawerManager = vectorComponent().notificationDrawerManager() with(vectorComponent()) {
notifiableEventResolver = vectorComponent().notifiableEventResolver() notificationDrawerManager = notificationDrawerManager()
pusherManager = vectorComponent().pusherManager() notifiableEventResolver = notifiableEventResolver()
activeSessionHolder = vectorComponent().activeSessionHolder() pusherManager = pusherManager()
vectorPreferences = vectorComponent().vectorPreferences() activeSessionHolder = activeSessionHolder()
vectorPreferences = vectorPreferences()
}
} }
/** /**
@ -73,6 +78,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* @param message the message * @param message the message
*/ */
override fun onMessageReceived(message: RemoteMessage) { 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()) { if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.i("Notification are disabled for this device") Timber.i("Notification are disabled for this device")
return return

View File

@ -19,20 +19,26 @@ import androidx.fragment.app.Fragment
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestAccountSettings
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings 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.TestPushRulesSettings
import im.vector.app.features.settings.troubleshoot.TestSystemSettings 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.TestFirebaseToken
import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices 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 im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration
import javax.inject.Inject import javax.inject.Inject
class NotificationTroubleshootTestManagerFactory @Inject constructor(private val testSystemSettings: TestSystemSettings, class NotificationTroubleshootTestManagerFactory @Inject constructor(
private val testAccountSettings: TestAccountSettings, private val testSystemSettings: TestSystemSettings,
private val testDeviceSettings: TestDeviceSettings, private val testAccountSettings: TestAccountSettings,
private val testBingRulesSettings: TestPushRulesSettings, private val testDeviceSettings: TestDeviceSettings,
private val testPlayServices: TestPlayServices, private val testBingRulesSettings: TestPushRulesSettings,
private val testFirebaseToken: TestFirebaseToken, private val testPlayServices: TestPlayServices,
private val testTokenRegistration: TestTokenRegistration) { private val testFirebaseToken: TestFirebaseToken,
private val testTokenRegistration: TestTokenRegistration,
private val testPushFromPushGateway: TestPushFromPushGateway,
private val testNotification: TestNotification
) {
fun create(fragment: Fragment): NotificationTroubleshootTestManager { fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment) val mgr = NotificationTroubleshootTestManager(fragment)
@ -43,6 +49,8 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
mgr.addTest(testPlayServices) mgr.addTest(testPlayServices)
mgr.addTest(testFirebaseToken) mgr.addTest(testFirebaseToken)
mgr.addTest(testTokenRegistration) mgr.addTest(testTokenRegistration)
mgr.addTest(testPushFromPushGateway)
mgr.addTest(testNotification)
return mgr return mgr
} }
} }

View File

@ -254,6 +254,10 @@
android:name=".features.call.service.CallHeadsUpActionReceiver" android:name=".features.call.service.CallHeadsUpActionReceiver"
android:exported="false" /> android:exported="false" />
<receiver
android:name=".features.settings.troubleshoot.TestNotificationReceiver"
android:exported="false" />
<!-- Exported false, should only be accessible from this app!! --> <!-- Exported false, should only be accessible from this app!! -->
<receiver <receiver
android:name=".features.notifications.NotificationBroadcastReceiver" android:name=".features.notifications.NotificationBroadcastReceiver"

View File

@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
import javax.net.ssl.SSLException import javax.net.ssl.SSLException
import javax.net.ssl.SSLPeerUnverifiedException import javax.net.ssl.SSLPeerUnverifiedException
@ -45,15 +44,12 @@ class DefaultErrorFormatter @Inject constructor(
when (throwable.ioException) { when (throwable.ioException) {
is SocketTimeoutException -> is SocketTimeoutException ->
stringProvider.getString(R.string.error_network_timeout) 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 -> is SSLPeerUnverifiedException ->
stringProvider.getString(R.string.login_error_ssl_peer_unverified) stringProvider.getString(R.string.login_error_ssl_peer_unverified)
is SSLException -> is SSLException ->
stringProvider.getString(R.string.login_error_ssl_other) stringProvider.getString(R.string.login_error_ssl_other)
else -> else ->
// TODO Check network state, airplane mode, etc.
stringProvider.getString(R.string.error_no_network) stringProvider.getString(R.string.error_no_network)
} }
} }

View File

@ -22,6 +22,7 @@ import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.abs import kotlin.math.abs
@ -34,6 +35,17 @@ class PushersManager @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val appNameProvider: AppNameProvider private val appNameProvider: AppNameProvider
) { ) {
fun testPush(pushKey: String, callback: MatrixCallback<Unit>): 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 { fun registerPusherWithFcmKey(pushKey: String): UUID {
val currentSession = activeSessionHolder.getActiveSession() val currentSession = activeSessionHolder.getActiveSession()
@ -56,4 +68,8 @@ class PushersManager @Inject constructor(
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id), callback) currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id), callback)
} }
companion object {
const val TEST_EVENT_ID = "\$THIS_IS_A_FAKE_EVENT_ID"
}
} }

View File

@ -30,7 +30,6 @@ import android.provider.Settings
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.app.R 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. * 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 * In android O will directly opens the notification settings, in lower version it will show the App settings
*/ */
fun startNotificationSettingsIntent(activity: AppCompatActivity, activityResultLauncher: ActivityResultLauncher<Intent>) { fun startNotificationSettingsIntent(context: Context, activityResultLauncher: ActivityResultLauncher<Intent>) {
val intent = Intent() val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
intent.putExtra(Settings.EXTRA_APP_PACKAGE, activity.packageName) intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
} else { } else {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
intent.putExtra("app_package", activity.packageName) intent.putExtra("app_package", context.packageName)
intent.putExtra("app_uid", activity.applicationInfo?.uid) intent.putExtra("app_uid", context.applicationInfo?.uid)
} }
activityResultLauncher.launch(intent) activityResultLauncher.launch(intent)
} }

View File

@ -28,6 +28,8 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.openUrlInChromeCustomTab import im.vector.app.core.utils.openUrlInChromeCustomTab
import kotlinx.android.synthetic.main.fragment_login_server_url_form.* 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 import javax.inject.Inject
/** /**
@ -115,7 +117,13 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
} }
override fun onError(throwable: Throwable) { 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) { override fun updateWithState(state: LoginViewState) {

View File

@ -590,6 +590,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
} }
} }
fun displayDiagnosticNotification() {
notificationUtils.displayDiagnosticNotification()
}
companion object { companion object {
private const val SUMMARY_NOTIFICATION_ID = 0 private const val SUMMARY_NOTIFICATION_ID = 0
private const val ROOM_MESSAGES_NOTIFICATION_ID = 1 private const val ROOM_MESSAGES_NOTIFICATION_ID = 1

View File

@ -27,8 +27,10 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
@ -36,6 +38,7 @@ import androidx.core.app.RemoteInput
import androidx.core.app.TaskStackBuilder import androidx.core.app.TaskStackBuilder
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.app.BuildConfig 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.HomeActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.RoomDetailArgs 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.VectorPreferences
import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -61,7 +64,6 @@ import kotlin.random.Random
@Singleton @Singleton
class NotificationUtils @Inject constructor(private val context: Context, class NotificationUtils @Inject constructor(private val context: Context,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val pinLocker: PinLocker,
private val vectorPreferences: VectorPreferences) { private val vectorPreferences: VectorPreferences) {
companion object { 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_SUMMARY_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DISMISS_SUMMARY_ACTION"
const val DISMISS_ROOM_NOTIF_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DISMISS_ROOM_NOTIF_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" 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 * 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 * Return true it the user has enabled the do not disturb mode
*/ */

View File

@ -16,12 +16,16 @@
package im.vector.app.features.settings package im.vector.app.features.settings
import android.app.Activity import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView 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.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment 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.rageshake.BugReporter
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TroubleshootTest import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.NotificationTroubleshootTestManagerFactory 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 import javax.inject.Inject
class VectorSettingsNotificationsTroubleshootFragment @Inject constructor( class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
@ -45,12 +52,16 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
@BindView(R.id.troubleshoot_test_recycler_view) @BindView(R.id.troubleshoot_test_recycler_view)
lateinit var mRecyclerView: RecyclerView lateinit var mRecyclerView: RecyclerView
@BindView(R.id.troubleshoot_bottom_view) @BindView(R.id.troubleshoot_bottom_view)
lateinit var mBottomView: ViewGroup lateinit var mBottomView: ViewGroup
@BindView(R.id.toubleshoot_summ_description) @BindView(R.id.toubleshoot_summ_description)
lateinit var mSummaryDescription: TextView lateinit var mSummaryDescription: TextView
@BindView(R.id.troubleshoot_summ_button) @BindView(R.id.troubleshoot_summ_button)
lateinit var mSummaryButton: Button lateinit var mSummaryButton: Button
@BindView(R.id.troubleshoot_run_button) @BindView(R.id.troubleshoot_run_button)
lateinit var mRunButton: Button lateinit var mRunButton: Button
@ -82,8 +93,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
} }
private fun startUI() { private fun startUI() {
mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_running_status, 0, 0)
0, 0)
testManager = testManagerFactory.create(this) testManager = testManagerFactory.create(this)
testManager?.statusListener = { troubleshootTestManager -> testManager?.statusListener = { troubleshootTestManager ->
if (isAdded) { if (isAdded) {
@ -94,10 +104,10 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
mSummaryButton.visibility = View.GONE mSummaryButton.visibility = View.GONE
mRunButton.visibility = View.VISIBLE mRunButton.visibility = View.VISIBLE
} }
TroubleshootTest.TestStatus.RUNNING -> { TroubleshootTest.TestStatus.RUNNING,
// Forces int type because it's breaking lint TroubleshootTest.TestStatus.WAITING_FOR_USER -> {
val size: Int = troubleshootTestManager.testList.size val size = troubleshootTestManager.testListSize
val currentTestIndex: Int = troubleshootTestManager.currentTestIndex val currentTestIndex = troubleshootTestManager.currentTestIndex
mSummaryDescription.text = getString( mSummaryDescription.text = getString(
R.string.settings_troubleshoot_diagnostic_running_status, R.string.settings_troubleshoot_diagnostic_running_status,
currentTestIndex, currentTestIndex,
@ -108,15 +118,7 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
} }
TroubleshootTest.TestStatus.FAILED -> { TroubleshootTest.TestStatus.FAILED -> {
// check if there are quick fixes // check if there are quick fixes
var hasQuickFix = false val hasQuickFix = testManager?.hasQuickFix().orFalse()
testManager?.testList?.let {
for (test in it) {
if (test.status == TroubleshootTest.TestStatus.FAILED && test.quickFix != null) {
hasQuickFix = true
break
}
}
}
if (hasQuickFix) { if (hasQuickFix) {
mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_with_quickfix) mSummaryDescription.text = getString(R.string.settings_troubleshoot_diagnostic_failure_status_with_quickfix)
} else { } else {
@ -161,6 +163,39 @@ class VectorSettingsNotificationsTroubleshootFragment @Inject constructor(
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_notification_troubleshoot) (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) { override fun onAttach(context: Context) {

View File

@ -77,6 +77,16 @@ class NotificationTroubleshootRecyclerViewAdapter(val tests: ArrayList<Troublesh
statusIconImage.visibility = View.VISIBLE statusIconImage.visibility = View.VISIBLE
statusIconImage.setImageResource(R.drawable.unit_test) statusIconImage.setImageResource(R.drawable.unit_test)
} }
TroubleshootTest.TestStatus.WAITING_FOR_USER -> {
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 -> { TroubleshootTest.TestStatus.RUNNING -> {
progressBar.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE
statusIconImage.visibility = View.INVISIBLE statusIconImage.visibility = View.INVISIBLE

View File

@ -23,13 +23,19 @@ import androidx.fragment.app.Fragment
import kotlin.properties.Delegates import kotlin.properties.Delegates
class NotificationTroubleshootTestManager(val fragment: Fragment) { class NotificationTroubleshootTestManager(val fragment: Fragment) {
private val testList = ArrayList<TroubleshootTest>()
val testListSize: Int
get() = testList.size
val testList = ArrayList<TroubleshootTest>()
var isCancelled = false var isCancelled = false
private set
var currentTestIndex by Delegates.observable(0) { _, _, _ -> var currentTestIndex by Delegates.observable(0) { _, _, _ ->
statusListener?.invoke(this) statusListener?.invoke(this)
} }
private set
val adapter = NotificationTroubleshootRecyclerViewAdapter(testList) val adapter = NotificationTroubleshootRecyclerViewAdapter(testList)
var statusListener: ((NotificationTroubleshootTestManager) -> Unit)? = null 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) { _, _, _ -> var diagStatus: TroubleshootTest.TestStatus by Delegates.observable(TroubleshootTest.TestStatus.NOT_STARTED) { _, _, _ ->
statusListener?.invoke(this) statusListener?.invoke(this)
} }
private set
fun addTest(test: TroubleshootTest) { fun addTest(test: TroubleshootTest) {
testList.add(test) testList.add(test)
@ -79,19 +86,31 @@ class NotificationTroubleshootTestManager(val fragment: Fragment) {
} }
fun retry(activityResultLauncher: ActivityResultLauncher<Intent>) { fun retry(activityResultLauncher: ActivityResultLauncher<Intent>) {
for (test in testList) { testList.forEach {
test.cancel() it.cancel()
test.description = null it.description = null
test.quickFix = null it.quickFix = null
test.status = TroubleshootTest.TestStatus.NOT_STARTED it.status = TroubleshootTest.TestStatus.NOT_STARTED
} }
runDiagnostic(activityResultLauncher) runDiagnostic(activityResultLauncher)
} }
fun cancel() { fun hasQuickFix(): Boolean {
isCancelled = true return testList.any { test ->
for (test in testList) { test.status == TroubleshootTest.TestStatus.FAILED && test.quickFix != null
test.cancel()
} }
} }
fun cancel() {
isCancelled = true
testList.forEach { it.cancel() }
}
fun onDiagnosticPushReceived() {
testList.forEach { it.onPushReceived() }
}
fun onDiagnosticNotificationClicked() {
testList.forEach { it.onNotificationClicked() }
}
} }

View File

@ -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<Intent>) {
// 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
}
}

View File

@ -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)
}
}

View File

@ -15,9 +15,9 @@
*/ */
package im.vector.app.features.settings.troubleshoot package im.vector.app.features.settings.troubleshoot
import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider 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. * 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) private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) { : TroubleshootTest(R.string.settings_troubleshoot_test_system_settings_title) {

View File

@ -25,6 +25,7 @@ abstract class TroubleshootTest(@StringRes val titleResId: Int) {
enum class TestStatus { enum class TestStatus {
NOT_STARTED, NOT_STARTED,
RUNNING, RUNNING,
WAITING_FOR_USER,
FAILED, FAILED,
SUCCESS SUCCESS
} }
@ -51,4 +52,10 @@ abstract class TroubleshootTest(@StringRes val titleResId: Int) {
open fun cancel() { open fun cancel() {
} }
open fun onPushReceived() {
}
open fun onNotificationClicked() {
}
} }

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/jitsi_layout" android:id="@+id/jitsi_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/black"> android:background="@android:color/black">
<!-- Note: A org.jitsi.meet.sdk.JitsiMeetView will be added here --> <!-- Note: A org.jitsi.meet.sdk.JitsiMeetView will be added here and so add tools:ignore="UselessParent" -->
<LinearLayout <LinearLayout
android:id="@+id/jitsi_progress_layout" android:id="@+id/jitsi_progress_layout"
@ -13,7 +14,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical"
tools:ignore="UselessParent">
<ProgressBar <ProgressBar
android:layout_width="40dp" android:layout_width="40dp"

View File

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_attachment_type_selector"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:baselineAligned="false"
android:orientation="horizontal"
android:weightSum="2">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:id="@+id/avatarCameraButton"
style="@style/AttachmentTypeSelectorButton"
android:contentDescription="@string/attachment_type_camera"
android:src="@drawable/ic_attachment_camera_white_24dp"
tools:background="@color/riotx_accent" />
<TextView
style="@style/AttachmentTypeSelectorLabel"
android:importantForAccessibility="no"
android:text="@string/attachment_type_camera" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<ImageButton
android:id="@+id/avatarGalleryButton"
style="@style/AttachmentTypeSelectorButton"
android:contentDescription="@string/attachment_type_gallery"
android:src="@drawable/ic_attachment_gallery_white_24dp"
tools:background="@color/riotx_accent" />
<TextView
style="@style/AttachmentTypeSelectorLabel"
android:importantForAccessibility="no"
android:text="@string/attachment_type_gallery" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -749,6 +749,15 @@
<string name="settings_troubleshoot_test_token_registration_success">FCM token successfully registered to HomeServer.</string> <string name="settings_troubleshoot_test_token_registration_success">FCM token successfully registered to HomeServer.</string>
<string name="settings_troubleshoot_test_token_registration_failed">Failed to register FCM token to HomeServer:\n%1$s</string> <string name="settings_troubleshoot_test_token_registration_failed">Failed to register FCM token to HomeServer:\n%1$s</string>
<string name="settings_troubleshoot_test_push_loop_title">Test Push</string>
<string name="settings_troubleshoot_test_push_loop_waiting_for_push">The application is waiting for the PUSH</string>
<string name="settings_troubleshoot_test_push_loop_success">The application is receiving PUSH</string>
<string name="settings_troubleshoot_test_push_loop_failed">Failed to receive push. Solution could be to reinstall the application.</string>
<string name="settings_troubleshoot_test_push_notification_content">You are viewing the notification! Click me!</string>
<string name="settings_troubleshoot_test_notification_title">Notification Display</string>
<string name="settings_troubleshoot_test_notification_notice">Please click on the notification. If you do not see the notification, please check the system settings.</string>
<string name="settings_troubleshoot_test_notification_notification_clicked">The notification has been clicked!</string>
<string name="settings_troubleshoot_test_foreground_service_started_title">Notifications Service</string> <string name="settings_troubleshoot_test_foreground_service_started_title">Notifications Service</string>
<string name="settings_troubleshoot_test_foreground_service_startedt_success">Notifications Service is running.</string> <string name="settings_troubleshoot_test_foreground_service_startedt_success">Notifications Service is running.</string>
<string name="settings_troubleshoot_test_foreground_service_started_failed">Notifications Service is not running.\nTry to restart the application.</string> <string name="settings_troubleshoot_test_foreground_service_started_failed">Notifications Service is not running.\nTry to restart the application.</string>