Merge branch 'develop' into feature/ons/add_members_fab

This commit is contained in:
Benoit Marty 2020-10-12 18:15:59 +02:00 committed by GitHub
commit dff7f24187
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1006 additions and 180 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

@ -11,17 +11,23 @@ Improvements 🙌:
- Small optimisation of scrolling experience in timeline (#2114) - Small optimisation of scrolling experience in timeline (#2114)
- Allow user to reset cross signing if he has no way to recover (#2052) - Allow user to reset cross signing if he has no way to recover (#2052)
- Create home shortcut for any room (#1525) - Create home shortcut for any room (#1525)
- Can't confirm email due to killing by Android (#2021)
- Add a menu item to open the setting in room list and in room (#2171) - Add a menu item to open the setting in room list and in room (#2171)
- Add a menu item in the timeline as a shortcut to invite user (#2171) - Add a menu item in the timeline as a shortcut to invite user (#2171)
- 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 FAB to room members list (#2226) - Add FAB to room members list (#2226)
- Add Sygnal API implementation to test is Push are correctly received
- Add PushGateway API implementation to test if Push are correctly received
- Cross signing: shouldn't offer to verify with other session when there is not. (#2227)
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)
- Fix Splash layout on small screens - Fix Splash layout on small screens
- Invalid popup when pressing back (#1635)
- Simplifies draft management and should fix bunch of draft issues (#952, #683) - Simplifies draft management and should fix bunch of draft issues (#952, #683)
- Very long topic cannot be fully visible (#1957)
Translations 🗣: Translations 🗣:
- -

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

@ -50,7 +50,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
@ -953,7 +953,7 @@ internal class DefaultCryptoService @Inject constructor(
roomEncryptorsStore.get(roomId) ?: /* No encrypting in this room */ return roomEncryptorsStore.get(roomId) ?: /* No encrypting in this room */ return
event.stateKey?.let { userId -> event.stateKey?.let { userId ->
val roomMember: RoomMemberSummary? = event.content.toModel() val roomMember: RoomMemberContent? = event.content.toModel()
val membership = roomMember?.membership val membership = roomMember?.membership
if (membership == Membership.JOIN) { if (membership == Membership.JOIN) {
// make sure we are tracking the deviceList for this user. // make sure we are tracking the deviceList for this user.

View File

@ -16,6 +16,9 @@
package org.matrix.android.sdk.internal.crypto.model.rest package org.matrix.android.sdk.internal.crypto.model.rest
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class SendToDeviceBody( internal data class SendToDeviceBody(
/** /**
* `Any` should implement [SendToDeviceObject], but we cannot use interface here because of Json serialization * `Any` should implement [SendToDeviceObject], but we cannot use interface here because of Json serialization

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

@ -94,9 +94,11 @@
</activity> </activity>
<activity android:name=".features.media.ImageMediaViewerActivity" /> <activity android:name=".features.media.ImageMediaViewerActivity" />
<!-- Add tools:ignore="Instantiatable" for the error reported only by Buildkite :/ -->
<activity <activity
android:name=".features.media.VectorAttachmentViewerActivity" android:name=".features.media.VectorAttachmentViewerActivity"
android:theme="@style/AppTheme.Transparent" /> android:theme="@style/AppTheme.Transparent"
tools:ignore="Instantiatable" />
<activity android:name=".features.media.BigImageViewerActivity" /> <activity android:name=".features.media.BigImageViewerActivity" />
<activity <activity
@ -252,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

@ -0,0 +1,94 @@
/*
* 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.core.epoxy
import android.animation.ObjectAnimator
import android.text.TextUtils
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.doOnPreDraw
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.extensions.copyOnLongClick
@EpoxyModelClass(layout = R.layout.item_expandable_textview)
abstract class ExpandableTextItem : VectorEpoxyModel<ExpandableTextItem.Holder>() {
@EpoxyAttribute
lateinit var content: String
@EpoxyAttribute
var maxLines: Int = 3
private var isExpanded = false
private var expandedLines = 0
override fun bind(holder: Holder) {
super.bind(holder)
holder.content.text = content
holder.content.copyOnLongClick()
holder.content.doOnPreDraw {
if (holder.content.lineCount > maxLines) {
expandedLines = holder.content.lineCount
holder.content.maxLines = maxLines
holder.view.setOnClickListener {
if (isExpanded) {
collapse(holder)
} else {
expand(holder)
}
}
holder.arrow.isVisible = true
} else {
holder.arrow.isVisible = false
}
}
}
private fun expand(holder: Holder) {
ObjectAnimator
.ofInt(holder.content, "maxLines", expandedLines)
.setDuration(200)
.start()
holder.content.ellipsize = null
holder.arrow.setImageResource(R.drawable.ic_expand_less)
holder.arrow.contentDescription = holder.view.context.getString(R.string.merged_events_collapse)
isExpanded = true
}
private fun collapse(holder: Holder) {
ObjectAnimator
.ofInt(holder.content, "maxLines", maxLines)
.setDuration(200)
.start()
holder.content.ellipsize = TextUtils.TruncateAt.END
holder.arrow.setImageResource(R.drawable.ic_expand_more)
holder.arrow.contentDescription = holder.view.context.getString(R.string.merged_events_expand)
isExpanded = false
}
class Holder : VectorEpoxyHolder() {
val content by bind<TextView>(R.id.expandableContent)
val arrow by bind<ImageView>(R.id.expandableArrow)
}
}

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

@ -76,7 +76,8 @@ data class VerificationBottomSheetViewState(
val userWantsToCancel: Boolean = false, val userWantsToCancel: Boolean = false,
val userThinkItsNotHim: Boolean = false, val userThinkItsNotHim: Boolean = false,
val quadSContainsSecrets: Boolean = true, val quadSContainsSecrets: Boolean = true,
val quadSHasBeenReset: Boolean = false val quadSHasBeenReset: Boolean = false,
val hasAnyOtherSession: Boolean = false
) : MvRxState ) : MvRxState
class VerificationBottomSheetViewModel @AssistedInject constructor( class VerificationBottomSheetViewModel @AssistedInject constructor(
@ -119,6 +120,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
session.cryptoService().verificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction session.cryptoService().verificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction
} }
val hasAnyOtherSession = session.cryptoService()
.getCryptoDeviceInfo(session.myUserId)
.any {
it.deviceId != session.sessionParams.deviceId
}
setState { setState {
copy( copy(
otherUserMxItem = userItem?.toMatrixItem(), otherUserMxItem = userItem?.toMatrixItem(),
@ -130,7 +137,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
roomId = args.roomId, roomId = args.roomId,
isMe = args.otherUserId == session.myUserId, isMe = args.otherUserId == session.myUserId,
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(), currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup() quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup(),
hasAnyOtherSession = hasAnyOtherSession
) )
} }

View File

@ -52,25 +52,32 @@ class VerificationRequestController @Inject constructor(
val matrixItem = viewState?.otherUserMxItem ?: return val matrixItem = viewState?.otherUserMxItem ?: return
if (state.selfVerificationMode) { if (state.selfVerificationMode) {
bottomSheetVerificationNoticeItem { if (state.hasAnyOtherSession) {
id("notice") bottomSheetVerificationNoticeItem {
notice(stringProvider.getString(R.string.verification_open_other_to_verify)) id("notice")
} notice(stringProvider.getString(R.string.verification_open_other_to_verify))
}
bottomSheetSelfWaitItem { bottomSheetSelfWaitItem {
id("waiting") id("waiting")
} }
dividerItem { dividerItem {
id("sep") id("sep")
}
} }
if (state.quadSContainsSecrets) { if (state.quadSContainsSecrets) {
val subtitle = if (state.hasAnyOtherSession) {
stringProvider.getString(R.string.verification_use_passphrase)
} else {
null
}
bottomSheetVerificationActionItem { bottomSheetVerificationActionItem {
id("passphrase") id("passphrase")
title(stringProvider.getString(R.string.verification_cannot_access_other_session)) title(stringProvider.getString(R.string.verification_cannot_access_other_session))
titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
subTitle(stringProvider.getString(R.string.verification_use_passphrase)) subTitle(subtitle)
iconRes(R.drawable.ic_arrow_right) iconRes(R.drawable.ic_arrow_right)
iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
listener { listener?.onClickRecoverFromPassphrase() } listener { listener?.onClickRecoverFromPassphrase() }
@ -122,13 +129,13 @@ class VerificationRequestController @Inject constructor(
listener { listener?.onClickOnVerificationStart() } listener { listener?.onClickOnVerificationStart() }
} }
} }
is Loading -> { is Loading -> {
bottomSheetVerificationWaitingItem { bottomSheetVerificationWaitingItem {
id("waiting") id("waiting")
title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName())) title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName()))
} }
} }
is Success -> { is Success -> {
if (!pr.invoke().isReady) { if (!pr.invoke().isReady) {
if (state.isMe) { if (state.isMe) {
bottomSheetVerificationWaitingItem { bottomSheetVerificationWaitingItem {

View File

@ -27,7 +27,7 @@ sealed class LoginAction : VectorViewModelAction {
data class UpdateSignMode(val signMode: SignMode) : LoginAction() data class UpdateSignMode(val signMode: SignMode) : LoginAction()
data class LoginWithToken(val loginToken: String) : LoginAction() data class LoginWithToken(val loginToken: String) : LoginAction()
data class WebLoginSuccess(val credentials: Credentials) : LoginAction() data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
data class InitWith(val loginConfig: LoginConfig) : LoginAction() data class InitWith(val loginConfig: LoginConfig?) : LoginAction()
data class ResetPassword(val email: String, val newPassword: String) : LoginAction() data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
object ResetPasswordMailConfirmed : LoginAction() object ResetPasswordMailConfirmed : LoginAction()

View File

@ -91,19 +91,19 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
addFirstFragment() addFirstFragment()
} }
// Get config extra
val loginConfig = intent.getParcelableExtra<LoginConfig?>(EXTRA_CONFIG)
if (loginConfig != null && isFirstCreation()) {
// TODO Check this
loginViewModel.handle(LoginAction.InitWith(loginConfig))
}
loginViewModel loginViewModel
.subscribe(this) { .subscribe(this) {
updateWithState(it) updateWithState(it)
} }
loginViewModel.observeViewEvents { handleLoginViewEvents(it) } loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
// Get config extra
val loginConfig = intent.getParcelableExtra<LoginConfig?>(EXTRA_CONFIG)
if (isFirstCreation()) {
// TODO Check this
loginViewModel.handle(LoginAction.InitWith(loginConfig))
}
} }
protected open fun addFirstFragment() { protected open fun addFirstFragment() {

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

@ -417,6 +417,18 @@ class LoginViewModel @AssistedInject constructor(
private fun handleInitWith(action: LoginAction.InitWith) { private fun handleInitWith(action: LoginAction.InitWith) {
loginConfig = action.loginConfig loginConfig = action.loginConfig
// If there is a pending email validation continue on this step
try {
if (registrationWizard?.isRegistrationStarted == true) {
currentThreePid?.let {
handle(LoginAction.PostViewEvent(LoginViewEvents.OnSendEmailSuccess(it)))
}
}
} catch (e: Throwable) {
// NOOP. API is designed to use wizards in a login/registration flow,
// but we need to check the state anyway.
}
} }
private fun handleResetPassword(action: LoginAction.ResetPassword) { private fun handleResetPassword(action: LoginAction.ResetPassword) {
@ -672,6 +684,7 @@ class LoginViewModel @AssistedInject constructor(
private fun onSessionCreated(session: Session) { private fun onSessionCreated(session: Session) {
activeSessionHolder.setActiveSession(session) activeSessionHolder.setActiveSession(session)
authenticationService.reset()
session.configureAndStart(applicationContext) session.configureAndStart(applicationContext)
setState { setState {
copy( copy(
@ -740,7 +753,7 @@ class LoginViewModel @AssistedInject constructor(
override fun onSuccess(data: LoginFlowResult) { override fun onSuccess(data: LoginFlowResult) {
when (data) { when (data) {
is LoginFlowResult.Success -> { is LoginFlowResult.Success -> {
val loginMode = when { val loginMode = when {
// SSO login is taken first // SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso

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

@ -19,6 +19,7 @@ package im.vector.app.features.roomprofile
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.expandableTextItem
import im.vector.app.core.epoxy.profiles.buildProfileAction import im.vector.app.core.epoxy.profiles.buildProfileAction
import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.epoxy.profiles.buildProfileSection
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
@ -57,6 +58,20 @@ class RoomProfileController @Inject constructor(
return return
} }
val roomSummary = data.roomSummary() ?: return val roomSummary = data.roomSummary() ?: return
// Topic
roomSummary
.topic
.takeIf { it.isNotEmpty() }
?.let {
buildProfileSection(stringProvider.getString(R.string.room_settings_topic))
expandableTextItem {
id("topic")
content(it)
maxLines(2)
}
}
// Security // Security
buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) buildProfileSection(stringProvider.getString(R.string.room_profile_section_security))
val learnMoreSubtitle = if (roomSummary.isEncrypted) { val learnMoreSubtitle = if (roomSummary.isEncrypted) {

View File

@ -129,7 +129,6 @@ class RoomProfileFragment @Inject constructor(
private fun setupLongClicks() { private fun setupLongClicks() {
roomProfileNameView.copyOnLongClick() roomProfileNameView.copyOnLongClick()
roomProfileAliasView.copyOnLongClick() roomProfileAliasView.copyOnLongClick()
roomProfileTopicView.copyOnLongClick()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -187,7 +186,6 @@ class RoomProfileFragment @Inject constructor(
roomProfileNameView.text = it.displayName roomProfileNameView.text = it.displayName
matrixProfileToolbarTitleView.text = it.displayName matrixProfileToolbarTitleView.text = it.displayName
roomProfileAliasView.setTextOrHide(it.canonicalAlias) roomProfileAliasView.setTextOrHide(it.canonicalAlias)
roomProfileTopicView.setTextOrHide(it.topic)
val matrixItem = it.toMatrixItem() val matrixItem = it.toMatrixItem()
avatarRenderer.render(matrixItem, roomProfileAvatarView) avatarRenderer.render(matrixItem, roomProfileAvatarView)
avatarRenderer.render(matrixItem, matrixProfileToolbarAvatarImageView) avatarRenderer.render(matrixItem, matrixProfileToolbarAvatarImageView)

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

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
</vector>

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

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/expandableContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
app:layout_constraintBottom_toTopOf="@+id/expandableArrow"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:maxLines="2"
tools:text="@sample/matrix.json/data/roomTopic" />
<ImageView
android:id="@+id/expandableArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:autoLink="web"
android:fontFamily="sans-serif"
android:gravity="center"
android:src="@drawable/ic_expand_more"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/expandableContent"
app:tint="?riotx_text_secondary"
tools:ignore="MissingPrefix" />
</androidx.constraintlayout.widget.ConstraintLayout>

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

@ -54,27 +54,9 @@
android:textAppearance="@style/Vector.Toolbar.Title" android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/roomProfileTopicView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomProfileNameView" app:layout_constraintTop_toBottomOf="@+id/roomProfileNameView"
tools:text="@sample/matrix.json/data/roomAlias" /> tools:text="@sample/matrix.json/data/roomAlias" />
<TextView
android:id="@+id/roomProfileTopicView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:autoLink="web"
android:fontFamily="sans-serif"
android:gravity="center"
android:textSize="14sp"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/roomProfileAliasView"
tools:text="@sample/matrix.json/data/roomTopic" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

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>