Merge pull request #6644 from vector-im/feature/mna/notification-tap-lls

[Location Share] Open maximized map on tapping on live sharing notification (PSG-616)
This commit is contained in:
Maxime NATUREL 2022-07-29 11:27:53 +02:00 committed by GitHub
commit c7d5ceca5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 35 deletions

1
changelog.d/6642.misc Normal file
View file

@ -0,0 +1 @@
[Location Share] Open maximized map on tapping on live sharing notification

View file

@ -377,7 +377,7 @@
</service>
<service
android:name=".features.location.LocationSharingAndroidService"
android:name=".features.location.live.tracking.LocationSharingAndroidService"
android:exported="false"
android:foregroundServiceType="location" />

View file

@ -42,6 +42,7 @@ import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.location.live.LiveLocationLabsFlagPromotionBottomSheet
import im.vector.app.features.location.live.duration.ChooseLiveDurationBottomSheet
import im.vector.app.features.location.live.tracking.LocationSharingAndroidService
import im.vector.app.features.location.option.LocationSharingOption
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.util.MatrixItem

View file

@ -22,6 +22,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.location.live.tracking.LocationSharingAndroidService
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn

View file

@ -24,6 +24,7 @@ import im.vector.app.R
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLocationSharingBinding
import im.vector.app.features.MainActivity
import kotlinx.parcelize.Parcelize
@Parcelize
@ -59,10 +60,15 @@ class LocationLiveMapViewActivity : VectorBaseActivity<ActivityLocationSharingBi
private const val EXTRA_LOCATION_LIVE_MAP_VIEW_ARGS = "EXTRA_LOCATION_LIVE_MAP_VIEW_ARGS"
fun getIntent(context: Context, locationLiveMapViewArgs: LocationLiveMapViewArgs): Intent {
return Intent(context, LocationLiveMapViewActivity::class.java).apply {
fun getIntent(context: Context, locationLiveMapViewArgs: LocationLiveMapViewArgs, firstStartMainActivity: Boolean = false): Intent {
val intent = Intent(context, LocationLiveMapViewActivity::class.java).apply {
putExtra(EXTRA_LOCATION_LIVE_MAP_VIEW_ARGS, locationLiveMapViewArgs)
}
return if (firstStartMainActivity) {
MainActivity.getIntentWithNextIntent(context, intent)
} else {
intent
}
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2022 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.location.live.tracking
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.core.app.TaskStackBuilder
import im.vector.app.R
import im.vector.app.core.extensions.createIgnoredUri
import im.vector.app.core.platform.PendingIntentCompat
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.time.Clock
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.location.live.map.LocationLiveMapViewActivity
import im.vector.app.features.location.live.map.LocationLiveMapViewArgs
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.themes.ThemeUtils
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class LiveLocationNotificationBuilder @Inject constructor(
private val context: Context,
private val stringProvider: StringProvider,
private val clock: Clock,
) {
/**
* Creates a notification that indicates the application is retrieving location even if it is in background or killed.
* @param roomId the id of the room where a live location is shared
*/
fun buildLiveLocationSharingNotification(roomId: String): Notification {
return NotificationCompat.Builder(context, NotificationUtils.SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(stringProvider.getString(R.string.live_location_sharing_notification_title))
.setContentText(stringProvider.getString(R.string.live_location_sharing_notification_description))
.setSmallIcon(R.drawable.ic_attachment_location_live_white)
.setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary))
.setCategory(NotificationCompat.CATEGORY_LOCATION_SHARING)
.setContentIntent(buildOpenLiveLocationMapIntent(roomId))
.build()
}
private fun buildOpenLiveLocationMapIntent(roomId: String): PendingIntent? {
val homeIntent = HomeActivity.newIntent(context, firstStartMainActivity = false)
val roomIntent = RoomDetailActivity.newIntent(context, TimelineArgs(roomId = roomId, switchToParentSpace = true), firstStartMainActivity = false)
val mapIntent = LocationLiveMapViewActivity.getIntent(
context = context,
locationLiveMapViewArgs = LocationLiveMapViewArgs(roomId = roomId),
firstStartMainActivity = true
)
mapIntent.action = NotificationUtils.TAP_TO_VIEW_ACTION
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
mapIntent.data = createIgnoredUri("openLiveLocationMap?$roomId")
// Recreate the back stack
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(homeIntent)
.addNextIntent(roomIntent)
.addNextIntent(mapIntent)
.getPendingIntent(
clock.epochMillis().toInt(),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE
)
}
}

View file

@ -14,18 +14,20 @@
* limitations under the License.
*/
package im.vector.app.features.location
package im.vector.app.features.location.live.tracking
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.os.Parcelable
import androidx.core.app.NotificationManagerCompat
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.services.VectorAndroidService
import im.vector.app.features.location.LocationData
import im.vector.app.features.location.LocationTracker
import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.redaction.CheckIfEventIsRedactedUseCase
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.CoroutineScope
@ -54,7 +56,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
val durationMillis: Long
) : Parcelable
@Inject lateinit var notificationUtils: NotificationUtils
@Inject lateinit var liveLocationNotificationBuilder: LiveLocationNotificationBuilder
@Inject lateinit var locationTracker: LocationTracker
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase
@ -62,13 +64,11 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
private val binder = LocalBinder()
/**
* Keep track of a map between beacon event Id starting the live and RoomArgs.
*/
private val roomArgsMap = mutableMapOf<String, RoomArgs>()
private val liveInfoSet = linkedSetOf<LiveInfo>()
var callback: Callback? = null
private val jobs = mutableListOf<Job>()
private var startInProgress = false
private var foregroundModeStarted = false
private val _roomIdsOfActiveLives = MutableSharedFlow<Set<String>>(replay = 1)
val roomIdsOfActiveLives = _roomIdsOfActiveLives.asSharedFlow()
@ -76,7 +76,6 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
override fun onCreate() {
super.onCreate()
Timber.i("onCreate")
initLocationTracking()
}
@ -102,8 +101,13 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
if (roomArgs != null) {
// Show a sticky notification
val notification = notificationUtils.buildLiveLocationSharingNotification()
startForeground(roomArgs.roomId.hashCode(), notification)
val notification = liveLocationNotificationBuilder.buildLiveLocationSharingNotification(roomArgs.roomId)
if (foregroundModeStarted) {
NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
} else {
startForeground(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
foregroundModeStarted = true
}
// Send beacon info state event
launchWithActiveSession { session ->
@ -148,15 +152,24 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
private fun stopSharingLocation(beaconEventId: String) {
Timber.i("stopSharingLocation for beacon $beaconEventId")
removeRoomArgs(beaconEventId)
updateNotification()
tryToDestroyMe()
}
private fun updateNotification() {
if (liveInfoSet.isNotEmpty()) {
val roomId = liveInfoSet.last().roomArgs.roomId
val notification = liveLocationNotificationBuilder.buildLiveLocationSharingNotification(roomId)
NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification)
}
}
private fun onLocationUpdate(locationData: LocationData) {
Timber.i("onLocationUpdate. Uncertainty: ${locationData.uncertainty}")
// Emit location update to all rooms in which live location sharing is active
roomArgsMap.toMap().forEach { item ->
sendLiveLocation(item.value.roomId, item.key, locationData)
liveInfoSet.toSet().forEach { liveInfo ->
sendLiveLocation(liveInfo.roomArgs.roomId, liveInfo.beaconEventId, locationData)
}
}
@ -183,7 +196,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
}
private fun tryToDestroyMe() {
if (startInProgress.not() && roomArgsMap.isEmpty()) {
if (startInProgress.not() && liveInfoSet.isEmpty()) {
Timber.i("Destroying self, time is up for all rooms")
stopSelf()
}
@ -199,13 +212,14 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
private fun addRoomArgs(beaconEventId: String, roomArgs: RoomArgs) {
Timber.i("adding roomArgs for beaconEventId: $beaconEventId")
roomArgsMap[beaconEventId] = roomArgs
liveInfoSet.removeAll { it.beaconEventId == beaconEventId }
liveInfoSet.add(LiveInfo(beaconEventId, roomArgs))
launchWithActiveSession { _roomIdsOfActiveLives.emit(getRoomIdsOfActiveLives()) }
}
private fun removeRoomArgs(beaconEventId: String) {
Timber.i("removing roomArgs for beaconEventId: $beaconEventId")
roomArgsMap.remove(beaconEventId)
liveInfoSet.removeAll { it.beaconEventId == beaconEventId }
launchWithActiveSession { _roomIdsOfActiveLives.emit(getRoomIdsOfActiveLives()) }
}
@ -234,7 +248,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
}
fun getRoomIdsOfActiveLives(): Set<String> {
return roomArgsMap.map { it.value.roomId }.toSet()
return liveInfoSet.map { it.roomArgs.roomId }.toSet()
}
override fun onBind(intent: Intent?): IBinder {
@ -251,5 +265,11 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca
companion object {
const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS"
private const val FOREGROUND_SERVICE_NOTIFICATION_ID = 300
}
private data class LiveInfo(
val beaconEventId: String,
val roomArgs: RoomArgs
)
}

View file

@ -105,7 +105,7 @@ class NotificationUtils @Inject constructor(
const val SMART_REPLY_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.SMART_REPLY_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"
private const val TAP_TO_VIEW_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.TAP_TO_VIEW_ACTION"
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"
@ -118,7 +118,7 @@ class NotificationUtils @Inject constructor(
private const val NOISY_NOTIFICATION_CHANNEL_ID = "DEFAULT_NOISY_NOTIFICATION_CHANNEL_ID"
private const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2"
const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2"
private const val CALL_NOTIFICATION_CHANNEL_ID = "CALL_NOTIFICATION_CHANNEL_ID_V2"
fun supportNotificationChannels() = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
@ -540,20 +540,6 @@ class NotificationUtils @Inject constructor(
return builder.build()
}
/**
* Creates a notification that indicates the application is retrieving location even if it is in background or killed.
*/
fun buildLiveLocationSharingNotification(): Notification {
return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
.setContentTitle(stringProvider.getString(R.string.live_location_sharing_notification_title))
.setContentText(stringProvider.getString(R.string.live_location_sharing_notification_description))
.setSmallIcon(R.drawable.ic_attachment_location_live_white)
.setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary))
.setCategory(NotificationCompat.CATEGORY_LOCATION_SHARING)
.setContentIntent(buildOpenHomePendingIntentForSummary())
.build()
}
/**
* Creates a notification that indicates the application is capturing the screen.
*/