Merge pull request #4354 from vector-im/feature/bma/shortcut_fixes

Shortcut fixes
This commit is contained in:
Benoit Marty 2021-10-28 10:13:02 +02:00 committed by GitHub
commit 2ce4d8d84f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 140 additions and 60 deletions

1
changelog.d/4170.bugfix Normal file
View file

@ -0,0 +1 @@
Do not show shortcuts if a PIN code is set

View file

@ -94,13 +94,15 @@ interface RoomService {
* Get a snapshot list of room summaries. * Get a snapshot list of room summaries.
* @return the immutable list of [RoomSummary] * @return the immutable list of [RoomSummary]
*/ */
fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
/** /**
* Get a live list of room summaries. This list is refreshed as soon as the data changes. * Get a live list of room summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of List[RoomSummary] * @return the [LiveData] of List[RoomSummary]
*/ */
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData<List<RoomSummary>>
/** /**
* Get a snapshot list of Breadcrumbs * Get a snapshot list of Breadcrumbs

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.space
import android.net.Uri import android.net.Uri
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
@ -74,9 +75,11 @@ interface SpaceService {
* Get a live list of space summaries. This list is refreshed as soon as the data changes. * Get a live list of space summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of List[SpaceSummary] * @return the [LiveData] of List[SpaceSummary]
*/ */
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>> fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>>
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
suspend fun joinSpace(spaceIdOrAlias: String, suspend fun joinSpace(spaceIdOrAlias: String,
reason: String? = null, reason: String? = null,

View file

@ -85,12 +85,14 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getRoomSummary(roomIdOrAlias) return roomSummaryDataSource.getRoomSummary(roomIdOrAlias)
} }
override fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> { override fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
return roomSummaryDataSource.getRoomSummaries(queryParams) sortOrder: RoomSortOrder): List<RoomSummary> {
return roomSummaryDataSource.getRoomSummaries(queryParams, sortOrder)
} }
override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> { override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
return roomSummaryDataSource.getRoomSummariesLive(queryParams) sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
return roomSummaryDataSource.getRoomSummariesLive(queryParams, sortOrder)
} }
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,

View file

@ -25,7 +25,6 @@ import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
@ -80,25 +79,27 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
} }
} }
fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> { fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary> {
return monarchy.fetchAllMappedSync( return monarchy.fetchAllMappedSync(
{ roomSummariesQuery(it, queryParams) }, { roomSummariesQuery(it, queryParams).process(sortOrder) },
{ roomSummaryMapper.map(it) } { roomSummaryMapper.map(it) }
) )
} }
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> { fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>> {
return monarchy.findAllMappedWithChanges( return monarchy.findAllMappedWithChanges(
{ {
roomSummariesQuery(it, queryParams) roomSummariesQuery(it, queryParams).process(sortOrder)
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
}, },
{ roomSummaryMapper.map(it) } { roomSummaryMapper.map(it) }
) )
} }
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>> { fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
return getRoomSummariesLive(queryParams) sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>> {
return getRoomSummariesLive(queryParams, sortOrder)
} }
fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? { fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? {
@ -122,8 +123,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
} }
} }
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> { fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
return getRoomSummaries(spaceSummaryQueryParams) sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary> {
return getRoomSummaries(spaceSummaryQueryParams, sortOrder)
} }
fun getRootSpaceSummaries(): List<RoomSummary> { fun getRootSpaceSummaries(): List<RoomSummary> {

View file

@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.GuestAccess
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.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
@ -94,12 +95,14 @@ internal class DefaultSpaceService @Inject constructor(
return spaceGetter.get(spaceId) return spaceGetter.get(spaceId)
} }
override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>> { override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
return roomSummaryDataSource.getSpaceSummariesLive(queryParams) sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
return roomSummaryDataSource.getSpaceSummariesLive(queryParams, sortOrder)
} }
override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> { override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams) sortOrder: RoomSortOrder): List<RoomSummary> {
return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams, sortOrder)
} }
override fun getRootSpaceSummaries(): List<RoomSummary> { override fun getRootSpaceSummaries(): List<RoomSummary> {

View file

@ -22,19 +22,28 @@ import android.os.Build
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinCodeStoreListener
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import io.reactivex.disposables.Disposables import io.reactivex.disposables.Disposables
import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
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.RoomSummary
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.asObservable
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class ShortcutsHandler @Inject constructor( class ShortcutsHandler @Inject constructor(
private val context: Context, private val context: Context,
private val shortcutCreator: ShortcutCreator, private val shortcutCreator: ShortcutCreator,
private val activeSessionHolder: ActiveSessionHolder private val activeSessionHolder: ActiveSessionHolder,
) { private val pinCodeStore: PinCodeStore
) : PinCodeStoreListener {
private val isRequestPinShortcutSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(context)
// Value will be set correctly if necessary
private var hasPinCode = true
fun observeRoomsAndBuildShortcuts(): Disposable { fun observeRoomsAndBuildShortcuts(): Disposable {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
@ -42,31 +51,56 @@ class ShortcutsHandler @Inject constructor(
return Disposables.empty() return Disposables.empty()
} }
return activeSessionHolder.getSafeActiveSession() hasPinCode = pinCodeStore.getEncodedPin() != null
?.getPagedRoomSummariesLive(
roomSummaryQueryParams { val session = activeSessionHolder.getSafeActiveSession() ?: return Disposables.empty()
memberships = listOf(Membership.JOIN) return session.getRoomSummariesLive(
}, roomSummaryQueryParams {
sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY memberships = listOf(Membership.JOIN)
) },
?.asObservable() sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY
?.subscribe { rooms -> )
.asObservable()
.doOnSubscribe { pinCodeStore.addListener(this) }
.doFinally { pinCodeStore.removeListener(this) }
.subscribe { rooms ->
// Remove dead shortcuts (i.e. deleted rooms) // Remove dead shortcuts (i.e. deleted rooms)
val roomIds = rooms.map { it.roomId } removeDeadShortcut(rooms.map { it.roomId })
val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC)
.map { it.id }
.filter { !roomIds.contains(it) }
ShortcutManagerCompat.removeLongLivedShortcuts(context, deadShortcutIds)
val shortcuts = rooms.mapIndexed { index, room -> // Create shortcuts
shortcutCreator.create(room, index) createShortcuts(rooms)
}
shortcuts.forEach { shortcut ->
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
}
} }
?: Disposables.empty() }
private fun removeDeadShortcut(roomIds: List<String>) {
val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC)
.map { it.id }
.filter { !roomIds.contains(it) }
if (deadShortcutIds.isNotEmpty()) {
Timber.d("Removing shortcut(s) $deadShortcutIds")
ShortcutManagerCompat.removeLongLivedShortcuts(context, deadShortcutIds)
if (isRequestPinShortcutSupported) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
context.getSystemService<ShortcutManager>()?.disableShortcuts(deadShortcutIds)
}
}
}
}
private fun createShortcuts(rooms: List<RoomSummary>) {
if (hasPinCode) {
// No shortcut in this case (privacy)
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
} else {
val shortcuts = rooms.mapIndexed { index, room ->
shortcutCreator.create(room, index)
}
shortcuts.forEach { shortcut ->
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
}
}
} }
fun clearShortcuts() { fun clearShortcuts() {
@ -82,7 +116,7 @@ class ShortcutsHandler @Inject constructor(
ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts) ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts)
// We can only disabled pinned shortcuts with the API, but at least it will prevent the crash // We can only disabled pinned shortcuts with the API, but at least it will prevent the crash
if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { if (isRequestPinShortcutSupported) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
context.getSystemService<ShortcutManager>() context.getSystemService<ShortcutManager>()
?.let { ?.let {
@ -91,4 +125,14 @@ class ShortcutsHandler @Inject constructor(
} }
} }
} }
override fun onPinSetUpChange(isConfigured: Boolean) {
hasPinCode = isConfigured
if (isConfigured) {
// Remove shortcuts immediately
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
}
// Else shortcut will be created next time any room summary is updated, or
// next time the app is started which is acceptable
}
} }

View file

@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@ -56,26 +57,40 @@ interface PinCodeStore {
* Will reset the counters * Will reset the counters
*/ */
fun resetCounters() fun resetCounters()
fun addListener(listener: PinCodeStoreListener)
fun removeListener(listener: PinCodeStoreListener)
} }
class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore { interface PinCodeStoreListener {
fun onPinSetUpChange(isConfigured: Boolean)
}
override suspend fun storeEncodedPin(encodePin: String) = withContext(Dispatchers.IO) { @Singleton
sharedPreferences.edit { class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore {
putString(ENCODED_PIN_CODE_KEY, encodePin) private val listeners = mutableSetOf<PinCodeStoreListener>()
override suspend fun storeEncodedPin(encodePin: String) {
withContext(Dispatchers.IO) {
sharedPreferences.edit {
putString(ENCODED_PIN_CODE_KEY, encodePin)
}
} }
listeners.forEach { it.onPinSetUpChange(isConfigured = true) }
} }
override suspend fun deleteEncodedPin() = withContext(Dispatchers.IO) { override suspend fun deleteEncodedPin() {
// Also reset the counters withContext(Dispatchers.IO) {
resetCounters() // Also reset the counters
sharedPreferences.edit { resetCounters()
remove(ENCODED_PIN_CODE_KEY) sharedPreferences.edit {
remove(ENCODED_PIN_CODE_KEY)
}
awaitPinCodeCallback<Boolean> {
PFSecurityManager.getInstance().pinCodeHelper.delete(it)
}
} }
awaitPinCodeCallback<Boolean> { listeners.forEach { it.onPinSetUpChange(isConfigured = false) }
PFSecurityManager.getInstance().pinCodeHelper.delete(it)
}
return@withContext
} }
override fun getEncodedPin(): String? { override fun getEncodedPin(): String? {
@ -124,6 +139,14 @@ class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences:
} }
} }
override fun addListener(listener: PinCodeStoreListener) {
listeners.add(listener)
}
override fun removeListener(listener: PinCodeStoreListener) {
listeners.remove(listener)
}
private suspend inline fun <T> awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback<T>) -> Unit) = suspendCoroutine<PFResult<T>> { cont -> private suspend inline fun <T> awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback<T>) -> Unit) = suspendCoroutine<PFResult<T>> { cont ->
callback(PFPinCodeHelperCallback<T> { result -> cont.resume(result) }) callback(PFPinCodeHelperCallback<T> { result -> cont.resume(result) })
} }