mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge branch 'develop' into feature/db_clean_up
This commit is contained in:
commit
3648d6292a
42
CHANGES.md
42
CHANGES.md
@ -1,6 +1,37 @@
|
||||
Changes in RiotX 0.23.0 (2020-XX-XX)
|
||||
Changes in Riot.imX 0.91.4 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
-
|
||||
|
||||
Improvements 🙌:
|
||||
-
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix crash when coming from a notification (#1601)
|
||||
- Fix Exception when importing keys (#1576)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
SDK API changes ⚠️:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Changes in Riot.imX 0.91.3 (2020-07-01)
|
||||
===================================================
|
||||
|
||||
Notes:
|
||||
- This version is the third beta version of RiotX codebase published as Riot-Android on the PlayStore.
|
||||
- Changelog below includes changes of v0.91.0, v0.91.1, and v0.91.2, because the first beta versions have been tagged and
|
||||
published from the branch feature/migration_from_legacy.
|
||||
- This version uses temporary name `Riot.imX`, to distinguish the app with RiotX app.
|
||||
|
||||
Features ✨:
|
||||
- Call with WebRTC support (##611)
|
||||
- Add capability to change the display name (#1529)
|
||||
@ -13,6 +44,8 @@ Improvements 🙌:
|
||||
- Update user avatar (#1054)
|
||||
- Allow self-signed certificate (#1564)
|
||||
- Improve file download and open in timeline
|
||||
- Catchup tab is removed temporarily (#1565)
|
||||
- Render room avatar change (#1319)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix dark theme issue on login screen (#1097)
|
||||
@ -21,12 +54,7 @@ Bugfix 🐛:
|
||||
- Use vendor prefix for non merged MSC (#1537)
|
||||
- Compress images before sending (#1333)
|
||||
- Searching by displayname is case sensitive (#1468)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
|
||||
SDK API changes ⚠️:
|
||||
-
|
||||
- Fix layout overlap issue (#1407)
|
||||
|
||||
Build 🧱:
|
||||
- Enable code optimization (Proguard)
|
||||
|
16
matrix-sdk-android/proguard-rules.pro
vendored
16
matrix-sdk-android/proguard-rules.pro
vendored
@ -64,3 +64,19 @@
|
||||
|
||||
### Webrtc
|
||||
-keep class org.webrtc.** { *; }
|
||||
|
||||
### Serializable persisted classes
|
||||
# https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
|
||||
-keepnames class * implements java.io.Serializable
|
||||
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
!static !transient <fields>;
|
||||
!private <fields>;
|
||||
!private <methods>;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
@ -127,6 +127,12 @@ interface Session :
|
||||
*/
|
||||
fun getSyncStateLive(): LiveData<SyncState>
|
||||
|
||||
/**
|
||||
* This method returns the current sync state.
|
||||
* @return the current [SyncState].
|
||||
*/
|
||||
fun getSyncState(): SyncState
|
||||
|
||||
/**
|
||||
* This methods return true if an initial sync has been processed
|
||||
*/
|
||||
|
@ -176,13 +176,14 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||
}
|
||||
}
|
||||
|
||||
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper2
|
||||
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
|
||||
realm.schema.get("OlmInboundGroupSessionEntity")
|
||||
?.transform { obj ->
|
||||
try {
|
||||
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
|
||||
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
|
||||
val newOlmInboundGroupSessionWrapper2 = OlmInboundGroupSessionWrapper2()
|
||||
val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
|
||||
val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
|
||||
.apply {
|
||||
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
|
||||
roomId = mxOlmInboundGroupSession2.mRoomId
|
||||
@ -191,7 +192,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
|
||||
}
|
||||
|
||||
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper2))
|
||||
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error")
|
||||
|
@ -175,7 +175,7 @@ internal object CertUtil {
|
||||
}
|
||||
}
|
||||
|
||||
val trustPinned = arrayOf<TrustManager>(PinnedTrustManager(hsConfig.allowedFingerprints, defaultTrustManager))
|
||||
val trustPinned = arrayOf<TrustManager>(PinnedTrustManagerProvider.provide(hsConfig.allowedFingerprints, defaultTrustManager))
|
||||
|
||||
val sslSocketFactory: SSLSocketFactory
|
||||
|
||||
@ -239,12 +239,12 @@ internal object CertUtil {
|
||||
fun newConnectionSpecs(hsConfig: HomeServerConnectionConfig): List<ConnectionSpec> {
|
||||
val builder = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||
val tlsVersions = hsConfig.tlsVersions
|
||||
if (null != tlsVersions) {
|
||||
if (null != tlsVersions && tlsVersions.isNotEmpty()) {
|
||||
builder.tlsVersions(*tlsVersions.toTypedArray())
|
||||
}
|
||||
|
||||
val tlsCipherSuites = hsConfig.tlsCipherSuites
|
||||
if (null != tlsCipherSuites) {
|
||||
if (null != tlsCipherSuites && tlsCipherSuites.isNotEmpty()) {
|
||||
builder.cipherSuites(*tlsCipherSuites.toTypedArray())
|
||||
}
|
||||
|
||||
@ -252,7 +252,8 @@ internal object CertUtil {
|
||||
builder.supportsTlsExtensions(hsConfig.shouldAcceptTlsExtensions)
|
||||
val list = ArrayList<ConnectionSpec>()
|
||||
list.add(builder.build())
|
||||
if (hsConfig.allowHttpExtension) {
|
||||
// TODO: we should display a warning if user enter an http url
|
||||
if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) {
|
||||
list.add(ConnectionSpec.CLEARTEXT)
|
||||
}
|
||||
return list
|
||||
|
@ -27,26 +27,23 @@ import javax.net.ssl.X509TrustManager
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param fingerprints An array of SHA256 cert fingerprints
|
||||
* @param fingerprints Not empty array of SHA256 cert fingerprints
|
||||
* @param defaultTrustManager Optional trust manager to fall back on if cert does not match
|
||||
* any of the fingerprints. Can be null.
|
||||
*/
|
||||
internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
|
||||
internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>,
|
||||
private val defaultTrustManager: X509TrustManager?) : X509TrustManager {
|
||||
|
||||
// Set to false to perform some test
|
||||
private val USE_DEFAULT_TRUST_MANAGER = true
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null && USE_DEFAULT_TRUST_MANAGER) {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, s)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isNullOrEmpty()) {
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
}
|
||||
@ -57,13 +54,13 @@ internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null && USE_DEFAULT_TRUST_MANAGER) {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, s)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints == null || fingerprints.isEmpty()) {
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
}
|
||||
@ -75,22 +72,12 @@ internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
|
||||
private fun checkTrusted(chain: Array<X509Certificate>) {
|
||||
val cert = chain[0]
|
||||
|
||||
var found = false
|
||||
if (fingerprints != null) {
|
||||
for (allowedFingerprint in fingerprints) {
|
||||
if (allowedFingerprint.matchesCert(cert)) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
if (!fingerprints.any { it.matchesCert(cert) }) {
|
||||
throw UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
||||
return emptyArray()
|
||||
return defaultTrustManager?.acceptedIssuers ?: emptyArray()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.matrix.android.internal.network.ssl
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.net.Socket
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.X509ExtendedTrustManager
|
||||
|
||||
/**
|
||||
* Implements a TrustManager that checks Certificates against an explicit list of known
|
||||
* fingerprints.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param fingerprints An array of SHA256 cert fingerprints
|
||||
* @param defaultTrustManager Optional trust manager to fall back on if cert does not match
|
||||
* any of the fingerprints. Can be null.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
internal class PinnedTrustManagerApi24(private val fingerprints: List<Fingerprint>,
|
||||
private val defaultTrustManager: X509ExtendedTrustManager?) : X509ExtendedTrustManager() {
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, engine: SSLEngine?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, authType, engine)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, authType, socket)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkClientTrusted(chain, authType)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, socket: Socket?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, authType, socket)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, engine: SSLEngine?) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, authType, engine)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
defaultTrustManager.checkServerTrusted(chain, s)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
}
|
||||
|
||||
checkTrusted(chain)
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
private fun checkTrusted(chain: Array<X509Certificate>) {
|
||||
val cert = chain[0]
|
||||
|
||||
if (!fingerprints.any { it.matchesCert(cert) }) {
|
||||
throw UnrecognizedCertificateException(cert, Fingerprint.newSha256Fingerprint(cert), null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
||||
return defaultTrustManager?.acceptedIssuers ?: emptyArray()
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.matrix.android.internal.network.ssl
|
||||
|
||||
import android.os.Build
|
||||
import javax.net.ssl.X509ExtendedTrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
internal object PinnedTrustManagerProvider {
|
||||
// Set to false to perform some tests
|
||||
private const val USE_DEFAULT_TRUST_MANAGER = true
|
||||
|
||||
fun provide(fingerprints: List<Fingerprint>?,
|
||||
defaultTrustManager: X509TrustManager?): X509TrustManager {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && defaultTrustManager is X509ExtendedTrustManager) {
|
||||
PinnedTrustManagerApi24(
|
||||
fingerprints.orEmpty(),
|
||||
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER }
|
||||
)
|
||||
} else {
|
||||
PinnedTrustManager(
|
||||
fingerprints.orEmpty(),
|
||||
defaultTrustManager.takeIf { USE_DEFAULT_TRUST_MANAGER }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.internal.session
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.LiveData
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
@ -45,7 +44,6 @@ import im.vector.matrix.android.api.session.securestorage.SecureStorageService
|
||||
import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
|
||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.terms.TermsService
|
||||
import im.vector.matrix.android.api.session.typing.TypingUsersTracker
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
@ -254,9 +252,9 @@ internal class DefaultSession @Inject constructor(
|
||||
eventBus.unregister(this)
|
||||
}
|
||||
|
||||
override fun getSyncStateLive(): LiveData<SyncState> {
|
||||
return getSyncThread().liveState()
|
||||
}
|
||||
override fun getSyncStateLive() = getSyncThread().liveState()
|
||||
|
||||
override fun getSyncState() = getSyncThread().currentState()
|
||||
|
||||
override fun hasAlreadySynced(): Boolean {
|
||||
return syncTokenStore.getLastToken() != null
|
||||
|
@ -65,6 +65,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
EventType.MESSAGE,
|
||||
EventType.STATE_ROOM_NAME,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
EventType.STATE_ROOM_AVATAR,
|
||||
EventType.STATE_ROOM_MEMBER,
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
EventType.CALL_INVITE,
|
||||
|
@ -104,7 +104,7 @@ abstract class SyncService : Service() {
|
||||
try {
|
||||
syncTask.execute(params)
|
||||
// Start sync if we were doing an initial sync and the syncThread is not launched yet
|
||||
if (isInitialSync && session.getSyncStateLive().value == SyncState.Idle) {
|
||||
if (isInitialSync && session.getSyncState() == SyncState.Idle) {
|
||||
val isForeground = !backgroundDetectionObserver.isInBackground
|
||||
session.startSync(isForeground)
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||
: Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
|
||||
|
||||
private var state: SyncState = SyncState.Idle
|
||||
private var liveState = MutableLiveData<SyncState>()
|
||||
private var liveState = MutableLiveData<SyncState>(state)
|
||||
private val lock = Object()
|
||||
private val syncScope = CoroutineScope(SupervisorJob())
|
||||
private val debouncer = Debouncer(createUIHandler())
|
||||
@ -98,6 +98,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||
lock.notify()
|
||||
}
|
||||
|
||||
fun currentState() = state
|
||||
|
||||
fun liveState(): LiveData<SyncState> {
|
||||
return liveState
|
||||
}
|
||||
|
@ -37,6 +37,8 @@
|
||||
<string name="notice_display_name_removed_by_you">You removed your display name (it was %1$s)</string>
|
||||
<string name="notice_room_topic_changed">%1$s changed the topic to: %2$s</string>
|
||||
<string name="notice_room_topic_changed_by_you">You changed the topic to: %1$s</string>
|
||||
<string name="notice_room_avatar_changed">%1$s changed the room avatar</string>
|
||||
<string name="notice_room_avatar_changed_by_you">You changed the room avatar</string>
|
||||
<string name="notice_room_name_changed">%1$s changed the room name to: %2$s</string>
|
||||
<string name="notice_room_name_changed_by_you">You changed the room name to: %1$s</string>
|
||||
<string name="notice_placed_video_call">%s placed a video call.</string>
|
||||
@ -71,6 +73,8 @@
|
||||
<string name="notice_room_name_removed_by_you">You removed the room name</string>
|
||||
<string name="notice_room_topic_removed">%1$s removed the room topic</string>
|
||||
<string name="notice_room_topic_removed_by_you">You removed the room topic</string>
|
||||
<string name="notice_room_avatar_removed">%1$s removed the room avatar</string>
|
||||
<string name="notice_room_avatar_removed_by_you">You removed the room avatar</string>
|
||||
<string name="notice_event_redacted">Message removed</string>
|
||||
<string name="notice_event_redacted_by">Message removed by %1$s</string>
|
||||
<string name="notice_event_redacted_with_reason">Message removed [reason: %1$s]</string>
|
||||
|
@ -17,7 +17,7 @@ androidExtensions {
|
||||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 0
|
||||
ext.versionMinor = 91
|
||||
ext.versionPatch = 2
|
||||
ext.versionPatch = 4
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
5
vector/proguard-rules.pro
vendored
5
vector/proguard-rules.pro
vendored
@ -20,4 +20,7 @@
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep class im.vector.riotx.features.** { *; }
|
||||
-keep class im.vector.riotx.features.** { *; }
|
||||
|
||||
## print all the rules in a file
|
||||
# -printconfiguration ../proguard_files/full-r8-config.txt
|
||||
|
@ -32,6 +32,7 @@ import im.vector.riotx.core.extensions.startSyncing
|
||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||
import im.vector.riotx.core.utils.deleteAllFiles
|
||||
import im.vector.riotx.features.home.HomeActivity
|
||||
import im.vector.riotx.features.home.ShortcutsHandler
|
||||
import im.vector.riotx.features.login.LoginActivity
|
||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
@ -82,6 +83,7 @@ class MainActivity : VectorBaseActivity() {
|
||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
@Inject lateinit var uiStateRepository: UiStateRepository
|
||||
@Inject lateinit var shortcutsHandler: ShortcutsHandler
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
@ -105,6 +107,9 @@ class MainActivity : VectorBaseActivity() {
|
||||
// Dismiss all notifications
|
||||
notificationDrawerManager.clearAllEvents()
|
||||
notificationDrawerManager.persistInfo()
|
||||
|
||||
// Also clear the dynamic shortcuts
|
||||
shortcutsHandler.clearShortcuts()
|
||||
}
|
||||
|
||||
private fun parseArgs(): MainActivityArgs {
|
||||
|
@ -117,8 +117,12 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
.observeOn(Schedulers.computation())
|
||||
.map { it.asSequence() }
|
||||
.subscribe { summaries ->
|
||||
val invites = summaries
|
||||
.filter { it.membership == Membership.INVITE }
|
||||
val invitesDm = summaries
|
||||
.filter { it.membership == Membership.INVITE && it.isDirect }
|
||||
.count()
|
||||
|
||||
val invitesRoom = summaries
|
||||
.filter { it.membership == Membership.INVITE && it.isDirect.not() }
|
||||
.count()
|
||||
|
||||
val peopleNotifications = summaries
|
||||
@ -139,12 +143,12 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
|
||||
setState {
|
||||
copy(
|
||||
notificationCountCatchup = peopleNotifications + roomsNotifications + invites,
|
||||
notificationCountCatchup = peopleNotifications + roomsNotifications + invitesDm + invitesRoom,
|
||||
notificationHighlightCatchup = peopleHasHighlight || roomsHasHighlight,
|
||||
notificationCountPeople = peopleNotifications,
|
||||
notificationHighlightPeople = peopleHasHighlight,
|
||||
notificationCountRooms = roomsNotifications,
|
||||
notificationHighlightRooms = roomsHasHighlight
|
||||
notificationCountPeople = peopleNotifications + invitesDm,
|
||||
notificationHighlightPeople = peopleHasHighlight || invitesDm > 0,
|
||||
notificationCountRooms = roomsNotifications + invitesRoom,
|
||||
notificationHighlightRooms = roomsHasHighlight || invitesRoom > 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.sync.SyncState
|
||||
data class HomeDetailViewState(
|
||||
val groupSummary: Option<GroupSummary> = Option.empty(),
|
||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||
val displayMode: RoomListDisplayMode = RoomListDisplayMode.HOME,
|
||||
val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE,
|
||||
val notificationCountCatchup: Int = 0,
|
||||
val notificationHighlightCatchup: Boolean = false,
|
||||
val notificationCountPeople: Int = 0,
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.riotx.features.home
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
@ -26,6 +27,7 @@ import im.vector.matrix.android.api.util.toMatrixItem
|
||||
import im.vector.riotx.core.glide.GlideApp
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import im.vector.riotx.features.home.room.detail.RoomDetailActivity
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import javax.inject.Inject
|
||||
@ -51,6 +53,11 @@ class ShortcutsHandler @Inject constructor(
|
||||
}
|
||||
|
||||
fun observeRoomsAndBuildShortcuts(): Disposable {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
// No op
|
||||
return Observable.empty<Unit>().subscribe()
|
||||
}
|
||||
|
||||
return homeRoomListStore
|
||||
.observe()
|
||||
.distinct()
|
||||
@ -78,6 +85,25 @@ class ShortcutsHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun clearShortcuts() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
// No op
|
||||
return
|
||||
}
|
||||
|
||||
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||
|
||||
// We can only disabled pinned shortcuts with the API, but at least it will prevent the crash
|
||||
if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
context.getSystemService(ShortcutManager::class.java)
|
||||
?.let {
|
||||
it.disableShortcuts(it.pinnedShortcuts.map { pinnedShortcut -> pinnedShortcut.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE API *********************************************************************************
|
||||
|
||||
private fun Bitmap.toProfileImageIcon(): IconCompat {
|
||||
|
@ -179,6 +179,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||
}
|
||||
EventType.STATE_ROOM_NAME,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
EventType.STATE_ROOM_AVATAR,
|
||||
EventType.STATE_ROOM_MEMBER,
|
||||
EventType.STATE_ROOM_ALIASES,
|
||||
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||
|
@ -48,6 +48,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||
EventType.STATE_ROOM_TOMBSTONE,
|
||||
EventType.STATE_ROOM_NAME,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
EventType.STATE_ROOM_AVATAR,
|
||||
EventType.STATE_ROOM_MEMBER,
|
||||
EventType.STATE_ROOM_ALIASES,
|
||||
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||
|
@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.GuestAccess
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomAvatarContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomGuestAccessContent
|
||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent
|
||||
@ -57,6 +58,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root)
|
||||
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||
@ -149,6 +151,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName)
|
||||
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
|
||||
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
|
||||
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(event, senderName)
|
||||
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName)
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName)
|
||||
EventType.CALL_INVITE,
|
||||
@ -220,6 +223,23 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatRoomAvatarEvent(event: Event, senderName: String?): CharSequence? {
|
||||
val content = event.getClearContent().toModel<RoomAvatarContent>() ?: return null
|
||||
return if (content.avatarUrl.isNullOrEmpty()) {
|
||||
if (event.isSentByCurrentUser()) {
|
||||
sp.getString(R.string.notice_room_avatar_removed_by_you)
|
||||
} else {
|
||||
sp.getString(R.string.notice_room_avatar_removed, senderName)
|
||||
}
|
||||
} else {
|
||||
if (event.isSentByCurrentUser()) {
|
||||
sp.getString(R.string.notice_room_avatar_changed_by_you)
|
||||
} else {
|
||||
sp.getString(R.string.notice_room_avatar_changed, senderName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
|
||||
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
|
||||
|
||||
|
@ -28,6 +28,7 @@ object TimelineDisplayableEvents {
|
||||
EventType.STATE_ROOM_WIDGET,
|
||||
EventType.STATE_ROOM_NAME,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
EventType.STATE_ROOM_AVATAR,
|
||||
EventType.STATE_ROOM_MEMBER,
|
||||
EventType.STATE_ROOM_ALIASES,
|
||||
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||
|
@ -30,8 +30,8 @@ class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) :
|
||||
return when (displayMode) {
|
||||
RoomListDisplayMode.HOME ->
|
||||
roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE || roomSummary.userDrafts.isNotEmpty()
|
||||
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
|
||||
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
|
||||
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership.isActive()
|
||||
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership.isActive()
|
||||
RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||
@SuppressLint("NewApi")
|
||||
fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
|
||||
// build the pending intent go to the home screen if this is clicked.
|
||||
val i = Intent(context, HomeActivity::class.java)
|
||||
val i = HomeActivity.newIntent(context)
|
||||
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
val pi = PendingIntent.getActivity(context, 0, i, 0)
|
||||
|
||||
@ -307,7 +307,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
|
||||
|
||||
val answerCallPendingIntent = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
||||
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
|
||||
.addNextIntent(VectorCallActivity.newIntent(
|
||||
context = context,
|
||||
callId = callId,
|
||||
@ -459,7 +459,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||
)
|
||||
|
||||
val contentPendingIntent = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
||||
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
|
||||
// TODO other userId
|
||||
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
|
||||
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
@ -651,7 +651,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||
stringProvider.getString(R.string.join),
|
||||
joinIntentPendingIntent)
|
||||
|
||||
val contentIntent = Intent(context, HomeActivity::class.java)
|
||||
val contentIntent = HomeActivity.newIntent(context)
|
||||
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
|
||||
contentIntent.data = Uri.parse("foobar://" + inviteNotifiableEvent.eventId)
|
||||
@ -689,7 +689,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||
.setColor(accentColor)
|
||||
.setAutoCancel(true)
|
||||
.apply {
|
||||
val contentIntent = Intent(context, HomeActivity::class.java)
|
||||
val contentIntent = HomeActivity.newIntent(context)
|
||||
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
|
||||
contentIntent.data = Uri.parse("foobar://" + simpleNotifiableEvent.eventId)
|
||||
@ -718,7 +718,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||
|
||||
// Recreate the back stack
|
||||
return TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
||||
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
|
||||
.addNextIntent(roomIntentTap)
|
||||
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
@ -171,6 +171,7 @@ object ThemeUtils {
|
||||
* @param resourceId the resource id in the light theme
|
||||
* @return the resource Id for the current theme
|
||||
*/
|
||||
// TODO Now that we are API 21, this is not necessary anymore
|
||||
fun getResourceId(c: Context, resourceId: Int): Int {
|
||||
val theme = getApplicationTheme(c)
|
||||
|
||||
|
@ -36,7 +36,7 @@ class SharedPreferencesUiStateRepository @Inject constructor(private val sharedP
|
||||
return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) {
|
||||
VALUE_DISPLAY_MODE_PEOPLE -> RoomListDisplayMode.PEOPLE
|
||||
VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS
|
||||
else -> RoomListDisplayMode.HOME
|
||||
else -> RoomListDisplayMode.PEOPLE // RoomListDisplayMode.HOME
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,13 +30,13 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/formSwitchSummary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/formSwitchDivider"
|
||||
app:layout_constraintEnd_toStartOf="@+id/formSwitchSwitch"
|
||||
app:layout_constraintStart_toStartOf="@+id/formSwitchTitle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/formSwitchTitle"
|
||||
tools:text="@string/create_room_public_description" />
|
||||
@ -46,7 +46,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="19dp"
|
||||
android:layout_marginRight="19dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/formSwitchDivider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
@ -5,7 +5,8 @@
|
||||
android:id="@+id/bottom_action_home"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_home_bottom_catchup"
|
||||
android:title="@string/bottom_action_home" />
|
||||
android:title="@string/bottom_action_home"
|
||||
android:visible="false" />
|
||||
|
||||
<item
|
||||
android:id="@+id/bottom_action_people"
|
||||
|
Loading…
Reference in New Issue
Block a user