Merge pull request #6475 from osmandapp/TelegramDataBase

Telegram database refactoring
This commit is contained in:
Alexey 2019-01-31 13:19:18 +03:00 committed by GitHub
commit 3a61240951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1577 additions and 1221 deletions

View file

@ -135,7 +135,7 @@ dependencies {
implementation project(path: ':OsmAnd-java', configuration: 'android')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation( "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version") {
implementation( "org.jetbrains.kotlin:kotlin-stdlib:1.2.71") {
exclude group: 'org.jetbrains', module: 'annotations'
}
implementation 'com.android.support:appcompat-v7:28.0.0-rc01'

View file

@ -96,7 +96,7 @@
android:layout_height="1dp"
android:background="@color/app_bar_divider" />
<LinearLayout
<FrameLayout
android:id="@+id/date_row"
android:layout_width="match_parent"
android:layout_height="@dimen/list_header_height"
@ -106,13 +106,12 @@
android:paddingRight="@dimen/content_padding_standard">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_start_btn"
android:layout_width="0dp"
android:id="@+id/date_btn"
android:layout_width="wrap_content"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/content_padding_half"
android:layout_marginRight="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round_border"
android:drawablePadding="@dimen/content_padding_half"
android:ellipsize="end"
@ -120,38 +119,12 @@
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:text="@string/start_date"
android:text="@string/shared_string_date"
android:textColor="?attr/ctrl_active_color"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium" />
<View
android:layout_width="16dp"
android:layout_height="1dp"
android:layout_gravity="center_vertical"
android:background="@color/app_bar_divider" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/date_end_btn"
android:layout_width="0dp"
android:layout_height="@dimen/dialog_button_height"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/content_padding_half"
android:layout_marginStart="@dimen/content_padding_half"
android:layout_weight="1"
android:background="@drawable/btn_round_border"
android:drawablePadding="@dimen/content_padding_half"
android:ellipsize="end"
android:gravity="start|center_vertical"
android:maxLines="1"
android:paddingLeft="@dimen/image_button_padding"
android:paddingRight="@dimen/image_button_padding"
android:text="@string/end_date"
android:textColor="?attr/ctrl_active_color"
android:textSize="@dimen/text_button_text_size"
app:typeface="@string/font_roboto_medium" />
</LinearLayout>
</FrameLayout>
</android.support.design.widget.AppBarLayout>

View file

@ -171,6 +171,63 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/gps_points"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_regular"
android:text="@string/gps_points" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/gps_points_collected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_mono_bold" />
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_regular"
android:text="@string/shared_string_collected" />
<net.osmand.telegram.ui.views.TextViewEx
android:id="@+id/gps_points_sent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/hint_text_size"
app:typeface="@string/font_roboto_mono_bold" />
<net.osmand.telegram.ui.views.TextViewEx
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/hint_text_size"
android:text="@string/shared_string_sent"
app:typeface="@string/font_roboto_regular" />
</LinearLayout>
</LinearLayout>
<Switch

View file

@ -1,4 +1,9 @@
<resources>
<string name="points_size">%1$d points</string>
<string name="shared_string_date">Date</string>
<string name="shared_string_collected">Collected</string>
<string name="gps_points">Gps points</string>
<string name="shared_string_sent">Sent</string>
<string name="monitoring_is_enabled">Monitoring is enabled</string>
<string name="monitoring_is_disabled">Monitoring is disabled</string>
<string name="time_on_the_move">time on the move</string>

View file

@ -24,8 +24,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
lateinit var notificationHelper: NotificationHelper private set
lateinit var osmandAidlHelper: OsmandAidlHelper private set
lateinit var locationProvider: TelegramLocationProvider private set
lateinit var messagesDbHelper: MessagesDbHelper private set
lateinit var savingTracksDbHelper: SavingTracksDbHelper private set
lateinit var locationMessages: LocationMessages private set
var telegramService: TelegramService? = null
@ -68,8 +67,7 @@ class TelegramApplication : Application(), OsmandHelperListener {
showLocationHelper = ShowLocationHelper(this)
notificationHelper = NotificationHelper(this)
locationProvider = TelegramLocationProvider(this)
messagesDbHelper = MessagesDbHelper(this)
savingTracksDbHelper = SavingTracksDbHelper(this)
locationMessages = LocationMessages(this)
if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) {
shareLocationHelper.startSharingLocation()
@ -96,6 +94,13 @@ class TelegramApplication : Application(), OsmandHelperListener {
return ni != null && ni.type == ConnectivityManager.TYPE_WIFI
}
val isMobileConnected: Boolean
get() {
val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val ni = mgr.activeNetworkInfo
return ni != null && ni.type == ConnectivityManager.TYPE_MOBILE
}
private val isInternetConnected: Boolean
get() {
val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

View file

@ -13,10 +13,11 @@ import android.os.*
import android.util.Log
import android.widget.Toast
import net.osmand.PlatformUtil
import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener
import net.osmand.telegram.helpers.TelegramHelper.TelegramIncomingMessagesListener
import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener
import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandLocationUtils
import org.drinkless.td.libcore.telegram.TdApi
import java.util.*
@ -274,6 +275,9 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) {
app().showLocationHelper.startShowMessagesTask(chatId, *messages)
messages.forEach {
app().locationMessages.addNewLocationMessage(it)
}
}
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {
@ -287,6 +291,10 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
override fun onUpdateMessages(messages: List<TdApi.Message>) {
messages.forEach {
app().settings.updateShareInfo(it)
app().shareLocationHelper.checkAndSendBufferMessagesToChat(it.chatId)
if (it.sendingState == null && (it.content is TdApi.MessageLocation || it.content is TdApi.MessageText)) {
app().locationMessages.addNewLocationMessage(it)
}
}
}

View file

@ -7,6 +7,7 @@ import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import net.osmand.PlatformUtil
import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.utils.AndroidUtils
@ -87,6 +88,8 @@ private const val GPS_UPDATE_EXPIRED_TIME = 60 * 3L // 3 minutes
class TelegramSettings(private val app: TelegramApplication) {
private val log = PlatformUtil.getLog(TelegramSettings::class.java)
private var shareChatsInfo = ConcurrentHashMap<Long, ShareChatInfo>()
private var hiddenOnMapChats: Set<Long> = emptySet()
private var shareDevices: Set<DeviceBot> = emptySet()
@ -242,21 +245,56 @@ class TelegramSettings(private val app: TelegramApplication) {
}
fun updateShareInfo(message: TdApi.Message) {
val shareChatInfo = shareChatsInfo[message.chatId]
val shareInfo = shareChatsInfo[message.chatId]
val content = message.content
if (shareChatInfo != null) {
if (shareInfo != null) {
when (content) {
is TdApi.MessageLocation -> {
shareChatInfo.currentMapMessageId = message.id
shareChatInfo.pendingMapMessage = false
val state = message.sendingState
if (state != null) {
if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) {
shareInfo.pendingMapMessage = true
log.debug("updateShareInfo MAP ${message.id} MessageSendingStatePending")
shareInfo.oldMapMessageId = message.id
} else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) {
shareInfo.hasSharingError = true
shareInfo.pendingMapMessage = false
log.debug("updateShareInfo MAP ${message.id} MessageSendingStateFailed")
}
} else {
shareInfo.currentMapMessageId = message.id
shareInfo.pendingMapMessage = false
shareInfo.pendingTdLib--
shareInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L
if (shareTypeValue == SHARE_TYPE_MAP) {
shareInfo.sentMessages++
}
log.debug("updateShareInfo MAP ${message.id} SUCCESS pendingTdLib: ${shareInfo.pendingTdLib}")
}
}
is TdApi.MessageText -> {
shareChatInfo.currentTextMessageId = message.id
shareChatInfo.updateTextMessageId++
shareChatInfo.pendingTextMessage = false
val state = message.sendingState
if (state != null) {
if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) {
log.debug("updateShareInfo TEXT ${message.id} MessageSendingStatePending")
shareInfo.pendingTextMessage = true
shareInfo.oldTextMessageId = message.id
} else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) {
log.debug("updateShareInfo TEXT ${message.id} MessageSendingStateFailed")
shareInfo.hasSharingError = true
shareInfo.pendingTextMessage = false
}
} else {
shareInfo.currentTextMessageId = message.id
shareInfo.updateTextMessageId++
shareInfo.pendingTextMessage = false
shareInfo.pendingTdLib--
shareInfo.sentMessages++
shareInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L
log.debug("updateShareInfo TEXT ${message.id} SUCCESS pendingTdLib: ${shareInfo.pendingTdLib}")
}
}
}
shareChatInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L
}
}
@ -540,6 +578,11 @@ class TelegramSettings(private val app: TelegramApplication) {
obj.put(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY, chatInfo.lastSuccessfulSendTimeMs)
obj.put(ShareChatInfo.LAST_SEND_MAP_TIME_KEY, chatInfo.lastSendMapMessageTime)
obj.put(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY, chatInfo.lastSendTextMessageTime)
obj.put(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY, chatInfo.pendingTextMessage)
obj.put(ShareChatInfo.PENDING_MAP_MESSAGE_KEY, chatInfo.pendingMapMessage)
obj.put(ShareChatInfo.COLLECTED_MESSAGES_KEY, chatInfo.collectedMessages)
obj.put(ShareChatInfo.SENT_MESSAGES_KEY, chatInfo.sentMessages)
obj.put(ShareChatInfo.PENDING_TDLIB_KEY, chatInfo.pendingTdLib)
jArray.put(obj)
}
jArray
@ -566,6 +609,11 @@ class TelegramSettings(private val app: TelegramApplication) {
lastSuccessfulSendTimeMs = obj.optLong(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY)
lastSendMapMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_MAP_TIME_KEY)
lastSendTextMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY)
pendingTextMessage = obj.optBoolean(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY)
pendingMapMessage = obj.optBoolean(ShareChatInfo.PENDING_MAP_MESSAGE_KEY)
collectedMessages = obj.optInt(ShareChatInfo.COLLECTED_MESSAGES_KEY)
sentMessages = obj.optInt(ShareChatInfo.SENT_MESSAGES_KEY)
pendingTdLib = obj.optInt(ShareChatInfo.PENDING_TDLIB_KEY)
}
shareChatsInfo[shareInfo.chatId] = shareInfo
}
@ -845,12 +893,17 @@ class TelegramSettings(private val app: TelegramApplication) {
var updateTextMessageId = 1
var currentMessageLimit = -1L
var currentMapMessageId = -1L
var oldMapMessageId = -1L
var currentTextMessageId = -1L
var oldTextMessageId = -1L
var userSetLivePeriod = -1L
var userSetLivePeriodStart = -1L
var lastSuccessfulSendTimeMs = -1L
var lastSendTextMessageTime = -1
var lastSendMapMessageTime = -1
var collectedMessages = 0
var sentMessages = 0
var pendingTdLib = 0
var pendingTextMessage = false
var pendingMapMessage = false
var shouldSendViaBotMessage = false
@ -887,6 +940,11 @@ class TelegramSettings(private val app: TelegramApplication) {
internal const val LAST_SUCCESSFUL_SEND_TIME_KEY = "lastSuccessfulSendTime"
internal const val LAST_SEND_MAP_TIME_KEY = "lastSendMapMessageTime"
internal const val LAST_SEND_TEXT_TIME_KEY = "lastSendTextMessageTime"
internal const val PENDING_TEXT_MESSAGE_KEY = "pendingTextMessage"
internal const val PENDING_MAP_MESSAGE_KEY = "pendingMapMessage"
internal const val COLLECTED_MESSAGES_KEY = "collectedMessages"
internal const val SENT_MESSAGES_KEY = "sentMessages"
internal const val PENDING_TDLIB_KEY = "sentMessages"
}
}
}

View file

@ -0,0 +1,347 @@
package net.osmand.telegram.helpers
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import net.osmand.PlatformUtil
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.utils.OsmandLocationUtils
import org.drinkless.td.libcore.telegram.TdApi
class LocationMessages(val app: TelegramApplication) {
private val log = PlatformUtil.getLog(LocationMessages::class.java)
// todo - bufferedMessages is for prepared/pending messages only. On app start we read prepared/pending messages to bufferedMessages. After status changed to sent/error - remove message from buffered.
private var bufferedMessages = emptyList<BufferMessage>()
private var lastMessages = emptyList<LocationMessage>()
private val dbHelper: SQLiteHelper
init {
dbHelper = SQLiteHelper(app)
readBufferedMessages()
}
fun getBufferedMessages(): List<BufferMessage> {
return bufferedMessages.sortedBy { it.time }
}
fun getBufferedMessagesForChat(chatId: Long): List<BufferMessage> {
return bufferedMessages.filter { it.chatId==chatId }.sortedBy { it.time }
}
// todo - read from db by date (Victor's suggestion - filter by one day only. Need to be changed in UI also.
fun getIngoingMessages(currentUserId: Int, start: Long, end: Long): List<LocationMessage> {
return dbHelper.getIngoingMessages(currentUserId, start, end)
}
fun getMessagesForUserInChat(userId: Int, chatId: Long, start: Long, end: Long): List<LocationMessage> {
return dbHelper.getMessagesForUserInChat(userId, chatId, start, end)
}
fun getMessagesForUser(userId: Int, start: Long, end: Long): List<LocationMessage> {
return dbHelper.getMessagesForUser(userId, start, end)
}
fun addBufferedMessage(message: BufferMessage) {
log.debug("addBufferedMessage $message")
val messages = mutableListOf(*this.bufferedMessages.toTypedArray())
messages.add(message)
this.bufferedMessages = messages
dbHelper.addBufferedMessage(message)
}
fun addNewLocationMessage(message: TdApi.Message) {
log.debug("addNewLocationMessage ${message.id}")
val type = OsmandLocationUtils.getMessageType(message, app.telegramHelper)
val previousMessage = lastMessages.firstOrNull { it.chatId == message.chatId && it.userId == message.senderUserId && it.type == type }
val locationMessage = OsmandLocationUtils.parseMessage(message, app.telegramHelper, previousMessage)
if (locationMessage != null) {
dbHelper.addLocationMessage(locationMessage)
val messages = mutableListOf(*this.lastMessages.toTypedArray())
messages.remove(previousMessage)
messages.add(locationMessage)
this.lastMessages = messages
}
}
fun clearBufferedMessages() {
log.debug("clearBufferedMessages")
dbHelper.clearBufferedMessages()
bufferedMessages = emptyList()
}
fun removeBufferedMessage(message: BufferMessage) {
log.debug("removeBufferedMessage $message")
val messages = mutableListOf(*this.bufferedMessages.toTypedArray())
messages.remove(message)
this.bufferedMessages = messages
dbHelper.removeBufferedMessage(message)
}
private fun readBufferedMessages() {
this.bufferedMessages = dbHelper.getBufferedMessages()
}
private fun readLastMessages() {
this.lastMessages = dbHelper.getLastMessages()
}
private class SQLiteHelper(context: Context) :
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(TIMELINE_TABLE_CREATE)
db.execSQL("CREATE INDEX $DATE_INDEX ON $TIMELINE_TABLE_NAME (\"$COL_TIME\" DESC);")
db.execSQL(BUFFER_TABLE_CREATE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL(TIMELINE_TABLE_DELETE)
db.execSQL(BUFFER_TABLE_DELETE)
onCreate(db)
}
internal fun addBufferedMessage(message: BufferMessage) {
writableDatabase?.execSQL(BUFFER_TABLE_INSERT,
arrayOf(message.chatId, message.lat, message.lon, message.altitude, message.speed,
message.hdop, message.bearing, message.time, message.type))
}
internal fun addLocationMessage(message: LocationMessage) {
writableDatabase?.execSQL(TIMELINE_TABLE_INSERT,
arrayOf(message.userId, message.chatId, message.lat, message.lon, message.altitude, message.speed,
message.hdop, message.bearing, message.time, message.type, message.messageId, message.distanceFromPrev))
}
internal fun getMessagesForUser(userId: Int, start: Long, end: Long): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_TIME ASC ",
arrayOf(userId.toString()))?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getPreviousMessage(userId: Int, chatId: Long): LocationMessage? {
var res:LocationMessage? = null
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_CHAT_ID = ? ORDER BY $COL_TIME DESC LIMIT 1",
arrayOf(userId.toString(), chatId.toString()))?.apply {
if (moveToFirst()) {
res = readLocationMessage(this@apply)
}
close()
}
return res
}
internal fun getIngoingMessages(currentUserId: Int, start: Long, end: Long): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID != ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_USER_ID ASC, $COL_CHAT_ID ASC, $COL_TIME ASC ",
arrayOf(currentUserId.toString()))?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getMessagesForUserInChat(userId: Int, chatId: Long, start: Long, end: Long): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(
"$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_CHAT_ID = ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_TIME ASC ",
arrayOf(userId.toString(), chatId.toString()))?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getBufferedMessages(): List<BufferMessage> {
val res = arrayListOf<BufferMessage>()
readableDatabase?.rawQuery(BUFFER_TABLE_SELECT, null)?.apply {
if (moveToFirst()) {
do {
res.add(readBufferMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun getLastMessages(): List<LocationMessage> {
val res = arrayListOf<LocationMessage>()
readableDatabase?.rawQuery(TIMELINE_TABLE_SELECT, null)?.apply {
if (moveToFirst()) {
do {
res.add(readLocationMessage(this@apply))
} while (moveToNext())
}
close()
}
return res
}
internal fun readLocationMessage(cursor: Cursor): LocationMessage {
val userId = cursor.getInt(0)
val chatId = cursor.getLong(1)
val lat = cursor.getDouble(2)
val lon = cursor.getDouble(3)
val altitude = cursor.getDouble(4)
val speed = cursor.getDouble(5)
val hdop = cursor.getDouble(6)
val bearing = cursor.getDouble(7)
val date = cursor.getLong(8)
val type = cursor.getInt(9)
val messageId = cursor.getLong(10)
val distanceFromPrev = cursor.getDouble(11)
return LocationMessage(userId, chatId, lat, lon, altitude, speed, hdop, bearing, date, type, messageId, distanceFromPrev)
}
internal fun readBufferMessage(cursor: Cursor): BufferMessage {
val chatId = cursor.getLong(0)
val lat = cursor.getDouble(1)
val lon = cursor.getDouble(2)
val altitude = cursor.getDouble(3)
val speed = cursor.getDouble(4)
val hdop = cursor.getDouble(5)
val bearing = cursor.getDouble(6)
val date = cursor.getLong(7)
val type = cursor.getInt(8)
return BufferMessage(chatId, lat, lon, altitude, speed, hdop, bearing, date, type)
}
internal fun clearBufferedMessages() {
writableDatabase?.execSQL(BUFFER_TABLE_CLEAR)
}
internal fun removeBufferedMessage(message: BufferMessage) {
writableDatabase?.execSQL(
BUFFER_TABLE_REMOVE,
arrayOf(
message.chatId,
message.lat,
message.lon,
message.altitude,
message.speed,
message.hdop,
message.bearing,
message.time,
message.type
)
)
}
companion object {
private const val DATABASE_NAME = "location_messages"
private const val DATABASE_VERSION = 5
private const val TIMELINE_TABLE_NAME = "timeline"
private const val BUFFER_TABLE_NAME = "buffer"
private const val COL_USER_ID = "user_id"
private const val COL_CHAT_ID = "chat_id"
private const val COL_TIME = "time"
private const val COL_LAT = "lat"
private const val COL_LON = "lon"
private const val COL_ALTITUDE = "altitude"
private const val COL_SPEED = "speed"
private const val COL_HDOP = "hdop"
private const val COL_BEARING = "bearing"
private const val COL_TYPE = "type" // 0 = user map message, 1 = user text message, 2 = bot map message, 3 = bot text message
private const val COL_MESSAGE_ID = "message_id"
private const val COL_DISTANCE_FROM_PREV = "distance_from_prev"
private const val DATE_INDEX = "date_index"
// Timeline messages table
private const val TIMELINE_TABLE_INSERT =
("INSERT INTO $TIMELINE_TABLE_NAME ($COL_USER_ID, $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE, $COL_MESSAGE_ID, $COL_DISTANCE_FROM_PREV) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
private const val TIMELINE_TABLE_CREATE =
("CREATE TABLE IF NOT EXISTS $TIMELINE_TABLE_NAME ($COL_USER_ID long, $COL_CHAT_ID long,$COL_LAT double, $COL_LON double, $COL_ALTITUDE double, $COL_SPEED float, $COL_HDOP double, $COL_BEARING double, $COL_TIME long, $COL_TYPE int, $COL_MESSAGE_ID long, $COL_DISTANCE_FROM_PREV double )")
private const val TIMELINE_TABLE_SELECT =
"SELECT $COL_USER_ID, $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE, $COL_MESSAGE_ID, $COL_DISTANCE_FROM_PREV FROM $TIMELINE_TABLE_NAME"
private const val TIMELINE_TABLE_CLEAR = "DELETE FROM $TIMELINE_TABLE_NAME"
private const val TIMELINE_TABLE_DELETE = "DROP TABLE IF EXISTS $TIMELINE_TABLE_NAME"
// Buffer messages table
private const val BUFFER_TABLE_INSERT =
("INSERT INTO $BUFFER_TABLE_NAME ($COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
private const val BUFFER_TABLE_CREATE =
("CREATE TABLE IF NOT EXISTS $BUFFER_TABLE_NAME ($COL_CHAT_ID long, $COL_LAT double, $COL_LON double, $COL_ALTITUDE double, $COL_SPEED float, $COL_HDOP double, $COL_BEARING double, $COL_TIME long, $COL_TYPE int)")
private const val BUFFER_TABLE_SELECT =
"SELECT $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE FROM $BUFFER_TABLE_NAME"
private const val BUFFER_TABLE_CLEAR = "DELETE FROM $BUFFER_TABLE_NAME"
private const val BUFFER_TABLE_REMOVE = "DELETE FROM $BUFFER_TABLE_NAME WHERE $COL_CHAT_ID = ? AND $COL_LAT = ? AND $COL_LON = ? AND $COL_ALTITUDE = ? AND $COL_SPEED = ? AND $COL_HDOP = ? AND $COL_BEARING = ? AND $COL_TIME = ? AND $COL_TYPE = ?"
private const val BUFFER_TABLE_DELETE = "DROP TABLE IF EXISTS $BUFFER_TABLE_NAME"
}
}
data class LocationMessage(
val userId: Int,
val chatId: Long,
val lat: Double,
val lon: Double,
val altitude: Double,
val speed: Double,
val hdop: Double,
val bearing: Double,
val time: Long,
val type: Int,
val messageId: Long,
val distanceFromPrev: Double)
data class BufferMessage (
val chatId: Long,
val lat: Double,
val lon: Double,
val altitude: Double,
val speed: Double,
val hdop: Double,
val bearing: Double,
val time: Long,
val type: Int)
companion object {
const val TYPE_USER_MAP = 0
const val TYPE_USER_TEXT = 1
const val TYPE_USER_BOTH = 2
const val TYPE_BOT_MAP = 3
const val TYPE_BOT_TEXT = 4
const val TYPE_BOT_BOTH = 5
}
}

View file

@ -1,122 +0,0 @@
package net.osmand.telegram.helpers
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import net.osmand.telegram.TelegramApplication
import org.drinkless.td.libcore.telegram.TdApi
class MessagesDbHelper(val app: TelegramApplication) {
private val messages = HashSet<Message>()
private val sqliteHelper: SQLiteHelper
init {
sqliteHelper = SQLiteHelper(app)
sqliteHelper.getMessages().forEach {
app.telegramHelper.loadMessage(it.chatId, it.messageId)
}
app.telegramHelper.addIncomingMessagesListener(object :
TelegramHelper.TelegramIncomingMessagesListener {
override fun onReceiveChatLocationMessages(
chatId: Long, vararg messages: TdApi.Message
) {
messages.forEach { addMessage(chatId, it.id) }
}
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {
messages.forEach { removeMessage(chatId, it.id) }
}
override fun updateLocationMessages() {}
})
}
fun saveMessages() {
clearMessages()
synchronized(messages) {
sqliteHelper.addMessages(messages)
}
}
fun clearMessages() {
sqliteHelper.clearMessages()
}
private fun addMessage(chatId: Long, messageId: Long) {
synchronized(messages) {
messages.add(Message(chatId, messageId))
}
}
private fun removeMessage(chatId: Long, messageId: Long) {
synchronized(messages) {
messages.remove(Message(chatId, messageId))
}
}
private class SQLiteHelper(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(MESSAGES_TABLE_CREATE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL(MESSAGES_TABLE_DELETE)
onCreate(db)
}
internal fun addMessages(messages: Set<Message>) {
messages.forEach {
writableDatabase?.execSQL(MESSAGES_TABLE_INSERT, arrayOf(it.chatId, it.messageId))
}
}
internal fun getMessages(): Set<Message> {
val res = HashSet<Message>()
readableDatabase?.rawQuery(MESSAGES_TABLE_SELECT, null)?.apply {
if (moveToFirst()) {
do {
res.add(Message(getLong(0), getLong(1)))
} while (moveToNext())
}
close()
}
return res
}
internal fun clearMessages() {
writableDatabase?.execSQL(MESSAGES_TABLE_CLEAR)
}
companion object {
private const val DB_NAME = "messages.db"
private const val DB_VERSION = 1
private const val MESSAGES_TABLE_NAME = "messages"
private const val MESSAGES_COL_CHAT_ID = "chat_id"
private const val MESSAGES_COL_MESSAGE_ID = "message_id"
private const val MESSAGES_TABLE_CREATE =
"CREATE TABLE IF NOT EXISTS $MESSAGES_TABLE_NAME (" +
"$MESSAGES_COL_CHAT_ID LONG, " +
"$MESSAGES_COL_MESSAGE_ID LONG)"
private const val MESSAGES_TABLE_DELETE = "DROP TABLE IF EXISTS $MESSAGES_TABLE_NAME"
private const val MESSAGES_TABLE_SELECT =
"SELECT $MESSAGES_COL_CHAT_ID, $MESSAGES_COL_MESSAGE_ID FROM $MESSAGES_TABLE_NAME"
private const val MESSAGES_TABLE_CLEAR = "DELETE FROM $MESSAGES_TABLE_NAME"
private const val MESSAGES_TABLE_INSERT = "INSERT INTO $MESSAGES_TABLE_NAME (" +
"$MESSAGES_COL_CHAT_ID, $MESSAGES_COL_MESSAGE_ID) VALUES (?, ?)"
}
}
private data class Message(val chatId: Long, val messageId: Long)
}

View file

@ -1,476 +0,0 @@
package net.osmand.telegram.helpers;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import net.osmand.PlatformUtil;
import net.osmand.telegram.TelegramApplication;
import net.osmand.telegram.utils.GPXUtilities;
import net.osmand.telegram.utils.GPXUtilities.GPXFile;
import net.osmand.telegram.utils.GPXUtilities.Track;
import net.osmand.telegram.utils.GPXUtilities.TrkSegment;
import net.osmand.telegram.utils.GPXUtilities.WptPt;
import org.apache.commons.logging.Log;
import org.drinkless.td.libcore.telegram.TdApi;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class SavingTracksDbHelper extends SQLiteOpenHelper {
private final static String DATABASE_NAME = "tracks";
private final static int DATABASE_VERSION = 3;
private final static String TRACK_NAME = "track"; //$NON-NLS-1$
private final static String TRACK_COL_USER_ID = "user_id"; //$NON-NLS-1$
private final static String TRACK_COL_CHAT_ID = "chat_id"; //$NON-NLS-1$
private final static String TRACK_COL_DATE = "date"; //$NON-NLS-1$
private final static String TRACK_COL_LAT = "lat"; //$NON-NLS-1$
private final static String TRACK_COL_LON = "lon"; //$NON-NLS-1$
private final static String TRACK_COL_ALTITUDE = "altitude"; //$NON-NLS-1$
private final static String TRACK_COL_SPEED = "speed"; //$NON-NLS-1$
private final static String TRACK_COL_HDOP = "hdop"; //$NON-NLS-1$
private final static String TRACK_COL_TEXT_INFO = "text_info"; // 1 = true, 0 = false //$NON-NLS-1$
private final static String INSERT_SCRIPT = "INSERT INTO " + TRACK_NAME + " (" + TRACK_COL_USER_ID + ", " + TRACK_COL_CHAT_ID + ", " + TRACK_COL_LAT + ", " + TRACK_COL_LON + ", "
+ TRACK_COL_ALTITUDE + ", " + TRACK_COL_SPEED + ", " + TRACK_COL_HDOP + ", " + TRACK_COL_DATE + ", " + TRACK_COL_TEXT_INFO + ")"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
private final static String CREATE_SCRIPT = "CREATE TABLE " + TRACK_NAME + " (" + TRACK_COL_USER_ID + " long," + TRACK_COL_CHAT_ID + " long," + TRACK_COL_LAT + " double, " + TRACK_COL_LON + " double, " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$
+ TRACK_COL_ALTITUDE + " double, " + TRACK_COL_SPEED + " double, " //$NON-NLS-1$ //$NON-NLS-2$
+ TRACK_COL_HDOP + " double, " + TRACK_COL_DATE + " long, " + TRACK_COL_TEXT_INFO + " int )";
private final static Log log = PlatformUtil.getLog(SavingTracksDbHelper.class);
private final TelegramApplication app;
public SavingTracksDbHelper(TelegramApplication app) {
super(app, DATABASE_NAME, null, DATABASE_VERSION);
this.app = app;
app.getTelegramHelper().addIncomingMessagesListener(new TelegramHelper.TelegramIncomingMessagesListener() {
@Override
public void onReceiveChatLocationMessages(long chatId, @NotNull TdApi.Message... messages) {
for (TdApi.Message message : messages) {
updateLocationMessage(message);
}
}
@Override
public void onDeleteChatLocationMessages(long chatId, @NotNull List<? extends TdApi.Message> messages) {
}
@Override
public void updateLocationMessages() {
}
});
app.getTelegramHelper().addOutgoingMessagesListener(new TelegramHelper.TelegramOutgoingMessagesListener() {
@Override
public void onUpdateMessages(@NotNull List<? extends TdApi.Message> messages) {
for (TdApi.Message message : messages) {
updateLocationMessage(message);
}
}
@Override
public void onDeleteMessages(long chatId, @NotNull List<Long> messages) {
}
@Override
public void onSendLiveLocationError(int code, @NotNull String message) {
}
});
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_SCRIPT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 3) {
db.execSQL("ALTER TABLE " + TRACK_NAME + " ADD " + TRACK_COL_TEXT_INFO + " int");
}
}
public void saveUserDataToGpx(SaveGpxListener listener, File dir, int userId, long chatId, long start, long end) {
GPXFile gpxFile = collectRecordedDataForUserAndChat(userId, chatId, start, end);
if (gpxFile != null && !gpxFile.isEmpty()) {
SaveGPXTrackToFileTask task = new SaveGPXTrackToFileTask(app, listener, gpxFile, dir, userId);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
public void saveGpx(SaveGpxListener listener, File dir, GPXFile gpxFile) {
if (gpxFile != null && !gpxFile.isEmpty()) {
SaveGPXTrackToFileTask task = new SaveGPXTrackToFileTask(app, listener, gpxFile, dir, 0);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private void updateLocationMessage(TdApi.Message message) {
log.debug(message);
TdApi.MessageContent content = message.content;
int senderId = app.getTelegramHelper().getSenderMessageId(message);
if (content instanceof TdApi.MessageLocation) {
long lastTextMessageUpdate = getLastTextTrackPointTimeForUser(message.senderUserId);
long currentTime = System.currentTimeMillis();
if (lastTextMessageUpdate == 0 || currentTime - lastTextMessageUpdate < 10 * 1000) {
log.debug("Add map message " + message.senderUserId);
TdApi.MessageLocation messageLocation = (TdApi.MessageLocation) content;
insertData(senderId, message.chatId, messageLocation.location.latitude,
messageLocation.location.longitude, 0.0, 0.0, 0.0,
Math.max(message.date, message.editDate), 0);
} else {
log.debug("Skip map message");
}
} else if (content instanceof TelegramHelper.MessageLocation) {
log.debug("Add text message " + message.senderUserId);
TelegramHelper.MessageLocation messageLocation = (TelegramHelper.MessageLocation) content;
insertData(senderId, message.chatId, messageLocation.getLat(), messageLocation.getLon(),
messageLocation.getAltitude(), messageLocation.getSpeed(), messageLocation.getHdop(),
messageLocation.getLastUpdated() * 1000L, 1);
}
}
private void insertData(int userId, long chatId, double lat, double lon, double alt, double speed, double hdop, long time, int textMessage) {
execWithClose(INSERT_SCRIPT, new Object[]{userId, chatId, lat, lon, alt, speed, hdop, time, textMessage});
}
private synchronized void execWithClose(String script, Object[] objects) {
SQLiteDatabase db = getWritableDatabase();
try {
if (db != null) {
db.execSQL(script, objects);
}
} catch (RuntimeException e) {
log.error(e.getMessage(), e);
} finally {
if (db != null) {
db.close();
}
}
}
private long getLastTextTrackPointTimeForUser(int userId) {
long res = 0;
try {
SQLiteDatabase db = getWritableDatabase();
if (db != null) {
try {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ? AND "
+ TRACK_COL_TEXT_INFO + " = ?" + " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId), String.valueOf(1)});
if (query.moveToFirst()) {
res = query.getLong(0);
}
query.close();
} finally {
db.close();
}
}
} catch (RuntimeException e) {
}
return res;
}
public GPXFile collectRecordedDataForUserAndChat(int userId, long chatId, long start, long end) {
GPXFile gpxFile = null;
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
gpxFile = collectDBTracksForUser(db, userId, chatId, start, end);
} finally {
db.close();
}
}
return gpxFile;
}
public GPXFile collectRecordedDataForUser(int userId, long chatId, long start, long end) {
GPXFile gpxFile = null;
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
if (chatId == 0) {
gpxFile = collectDBTracksForUser(db, userId, start, end);
} else {
gpxFile = collectDBTracksForUser(db, userId, chatId, start, end);
}
} finally {
db.close();
}
}
return gpxFile;
}
public ArrayList<GPXFile> collectRecordedDataForUsers(long start, long end, ArrayList<Integer> ignoredUsersIds) {
ArrayList<GPXFile> data = new ArrayList<>();
SQLiteDatabase db = getReadableDatabase();
if (db != null && db.isOpen()) {
try {
collectDBTracksForUsers(db, data, start, end, ignoredUsersIds);
} finally {
db.close();
}
}
return data;
}
private GPXFile collectDBTracksForUser(SQLiteDatabase db, int userId, long chatId, long start, long end) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ?"
+ " AND " + TRACK_COL_CHAT_ID + " = ?" + " AND " + TRACK_COL_DATE + " BETWEEN " + start + " AND " + end
+ " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId), String.valueOf(chatId)});
GPXFile gpxFile = null;
long previousTime = 0;
TrkSegment segment = null;
Track track = null;
if (query.moveToFirst()) {
gpxFile = new GPXFile();
gpxFile.chatId = chatId;
gpxFile.userId = userId;
do {
long time = query.getLong(7);
WptPt pt = new WptPt();
pt.userId = query.getInt(0);
pt.chatId = query.getLong(1);
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpxFile.tracks.add(track);
}
previousTime = time;
} while (query.moveToNext());
}
query.close();
return gpxFile;
}
private GPXFile collectDBTracksForUser(SQLiteDatabase db, int userId, long start, long end) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_USER_ID + " = ?"
+ " AND " + TRACK_COL_DATE + " BETWEEN " + start + " AND " + end
+ " ORDER BY " + TRACK_COL_DATE + " ASC ", new String[]{String.valueOf(userId)});
GPXFile gpxFile = null;
long previousTime = 0;
TrkSegment segment = null;
Track track = null;
if (query.moveToFirst()) {
gpxFile = new GPXFile();
gpxFile.userId = userId;
do {
long time = query.getLong(7);
WptPt pt = new WptPt();
pt.userId = query.getInt(0);
pt.chatId = query.getLong(1);
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpxFile.tracks.add(track);
}
previousTime = time;
} while (query.moveToNext());
}
query.close();
return gpxFile;
}
private void collectDBTracksForUsers(SQLiteDatabase db, ArrayList<GPXFile> dataTracks, long start, long end, ArrayList<Integer> ignoredUsersIds) {
Cursor query = db.rawQuery("SELECT " + TRACK_COL_USER_ID + "," + TRACK_COL_CHAT_ID + ","
+ TRACK_COL_LAT + "," + TRACK_COL_LON + "," + TRACK_COL_ALTITUDE + "," + TRACK_COL_SPEED + ","
+ TRACK_COL_HDOP + "," + TRACK_COL_DATE + " FROM " + TRACK_NAME + " WHERE " + TRACK_COL_DATE
+ " BETWEEN " + start + " AND " + end + " ORDER BY " + TRACK_COL_USER_ID + " ASC, "
+ TRACK_COL_CHAT_ID + " ASC, " + TRACK_COL_DATE + " ASC ", null);
long previousTime = 0;
long previousChatId = 0;
int previousUserId = 0;
TrkSegment segment = null;
Track track = null;
GPXFile gpx = new GPXFile();
if (query.moveToFirst()) {
do {
int userId = query.getInt(0);
if (ignoredUsersIds.contains(userId)) {
continue;
}
int chatId = query.getInt(1);
long time = query.getLong(7);
if (previousUserId != userId || previousChatId != chatId) {
gpx = new GPXFile();
gpx.chatId = chatId;
gpx.userId = userId;
previousTime = 0;
track = null;
segment = null;
dataTracks.add(gpx);
}
WptPt pt = new WptPt();
pt.userId = userId;
pt.chatId = chatId;
pt.lat = query.getDouble(2);
pt.lon = query.getDouble(3);
pt.ele = query.getDouble(4);
pt.speed = query.getDouble(5);
pt.hdop = query.getDouble(6);
pt.time = time;
long currentInterval = Math.abs(time - previousTime);
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment.points.add(pt);
} else {
segment = new TrkSegment();
segment.points.add(pt);
track.segments.add(segment);
}
} else {
track = new Track();
segment = new TrkSegment();
track.segments.add(segment);
segment.points.add(pt);
gpx.tracks.add(track);
}
previousTime = time;
previousUserId = userId;
previousChatId = chatId;
} while (query.moveToNext());
}
query.close();
}
private static class SaveGPXTrackToFileTask extends AsyncTask<Void, Void, List<String>> {
private TelegramApplication app;
private SaveGpxListener listener;
private final GPXFile gpxFile;
private File dir;
private int userId;
SaveGPXTrackToFileTask(TelegramApplication app, SaveGpxListener listener, GPXFile gpxFile, File dir, int userId) {
this.gpxFile = gpxFile;
this.listener = listener;
this.app = app;
this.dir = dir;
this.userId = userId;
}
@Override
protected List<String> doInBackground(Void... params) {
List<String> warnings = new ArrayList<String>();
dir.mkdirs();
if (dir.getParentFile().canWrite()) {
if (dir.exists()) {
// save file
File fout = new File(dir, userId + ".gpx"); //$NON-NLS-1$
if (!gpxFile.isEmpty()) {
WptPt pt = gpxFile.findPointToShow();
TdApi.User user = app.getTelegramHelper().getUser(pt.userId);
String fileName;
if (user != null) {
fileName = TelegramUiHelper.INSTANCE.getUserName(user)
+ "_" + new SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(new Date(pt.time)); //$NON-NLS-1$
} else {
fileName = userId + "_" + new SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(new Date(pt.time)); //$NON-NLS-1$
}
fout = new File(dir, fileName + ".gpx"); //$NON-NLS-1$
int ind = 1;
while (fout.exists()) {
fout = new File(dir, fileName + "_" + (++ind) + ".gpx"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
String warn = GPXUtilities.writeGpxFile(fout, gpxFile, app);
if (warn != null) {
warnings.add(warn);
return warnings;
}
}
}
return warnings;
}
@Override
protected void onPostExecute(List<String> warnings) {
if (listener != null) {
if (warnings != null && warnings.isEmpty()) {
listener.onSavingGpxFinish(gpxFile.path);
} else {
listener.onSavingGpxError(warnings);
}
}
}
}
public interface SaveGpxListener {
void onSavingGpxFinish(String path);
void onSavingGpxError(List<String> warnings);
}
}

View file

@ -3,6 +3,7 @@ package net.osmand.telegram.helpers
import net.osmand.Location
import net.osmand.PlatformUtil
import net.osmand.telegram.*
import net.osmand.telegram.helpers.LocationMessages.BufferMessage
import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidNetworkUtils
import net.osmand.telegram.utils.BASE_URL
@ -48,36 +49,8 @@ class ShareLocationHelper(private val app: TelegramApplication) {
lastLocation = location
if (location != null) {
val chatsShareInfo = app.settings.getChatsShareInfo()
if (chatsShareInfo.isNotEmpty()) {
val latitude = location.latitude
val longitude = location.longitude
val user = app.telegramHelper.getCurrentUser()
val sharingMode = app.settings.currentSharingMode
if (user != null && sharingMode == user.id.toString()) {
when (app.settings.shareTypeValue) {
SHARE_TYPE_MAP -> app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, latitude, longitude)
SHARE_TYPE_TEXT -> app.telegramHelper.sendLiveLocationText(chatsShareInfo, location)
SHARE_TYPE_MAP_AND_TEXT -> {
app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, latitude, longitude)
app.telegramHelper.sendLiveLocationText(chatsShareInfo, location)
}
}
} else if (sharingMode.isNotEmpty()) {
val url = getDeviceSharingUrl(location,sharingMode)
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
object : AndroidNetworkUtils.OnRequestResultListener {
override fun onResult(result: String?) {
updateShareInfoSuccessfulSendTime(result, chatsShareInfo)
val osmandBot = app.telegramHelper.getOsmandBot()
if (osmandBot != null) {
checkAndSendViaBotMessages(chatsShareInfo, TdApi.Location(latitude, longitude), osmandBot)
}
}
})
}
if (app.settings.getChatsShareInfo().isNotEmpty()) {
shareLocationMessages(location, app.telegramHelper.getCurrentUserId())
}
lastLocationMessageSentTime = System.currentTimeMillis()
}
@ -122,6 +95,37 @@ class ShareLocationHelper(private val app: TelegramApplication) {
}
}
fun checkAndSendBufferMessagesToChat(chatId: Long) {
val shareInfo = app.settings.getChatsShareInfo()[chatId]
if (shareInfo != null && shareInfo.pendingTdLib < 10) {
app.locationMessages.getBufferedMessagesForChat(shareInfo.chatId).forEach {
if (it.type == LocationMessages.TYPE_USER_TEXT && !shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
app.telegramHelper.editTextLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
} else if (it.type == LocationMessages.TYPE_USER_MAP && !shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
app.telegramHelper.editMapLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
} else if (it.type == LocationMessages.TYPE_USER_BOTH) {
var messageSent = false
if (!shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
app.telegramHelper.editMapLocation(shareInfo, it)
messageSent = true
}
if (!shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
app.telegramHelper.editTextLocation(shareInfo, it)
messageSent = true
}
if (messageSent) {
app.locationMessages.removeBufferedMessage(it)
}
}
if (shareInfo.pendingTdLib >= 10) {
return
}
}
}
}
fun startSharingLocation() {
if (!sharingLocation) {
sharingLocation = true
@ -129,6 +133,8 @@ class ShareLocationHelper(private val app: TelegramApplication) {
app.startMyLocationService()
refreshNotification()
checkAndSendBufferMessages()
} else {
app.forceUpdateMyLocation()
}
@ -158,25 +164,193 @@ class ShareLocationHelper(private val app: TelegramApplication) {
refreshNotification()
}
private fun getDeviceSharingUrl(loc: Location, sharingMode: String): String {
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.latitude}&lon=${loc.longitude}"
private fun checkAndSendBufferMessages(){
log.debug("checkAndSendBufferMessages")
app.settings.getChatsShareInfo().forEach loop@{ (chatId, shareInfo) ->
if (shareInfo.pendingTdLib < 10) {
app.locationMessages.getBufferedMessagesForChat(chatId).forEach {
if (it.type == LocationMessages.TYPE_USER_TEXT && !shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) {
app.telegramHelper.editTextLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
} else if (it.type == LocationMessages.TYPE_USER_MAP && !shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) {
app.telegramHelper.editMapLocation(shareInfo, it)
app.locationMessages.removeBufferedMessage(it)
}
if (shareInfo.pendingTdLib >= 10) {
return@loop
}
}
}
}
}
private fun shareLocationMessages(location: Location, userId: Int) {
val chatsShareInfo = app.settings.getChatsShareInfo()
val latitude = location.latitude
val longitude = location.longitude
val sharingMode = app.settings.currentSharingMode
val isBot = sharingMode != userId.toString()
var bufferedMessagesFull = false
val type = when (app.settings.shareTypeValue) {
SHARE_TYPE_MAP -> {
if (isBot) LocationMessages.TYPE_BOT_MAP else LocationMessages.TYPE_USER_MAP
}
SHARE_TYPE_TEXT -> {
if (isBot) LocationMessages.TYPE_BOT_TEXT else LocationMessages.TYPE_USER_TEXT
}
SHARE_TYPE_MAP_AND_TEXT -> {
if (isBot) LocationMessages.TYPE_BOT_BOTH else LocationMessages.TYPE_USER_BOTH
} else -> -1
}
chatsShareInfo.values.forEach { shareInfo ->
if (shareInfo.pendingTdLib >= 10) {
bufferedMessagesFull = true
}
val message = BufferMessage(
shareInfo.chatId, latitude, longitude, location.altitude, location.speed.toDouble(),
location.accuracy.toDouble(), location.bearing.toDouble(), System.currentTimeMillis(), type
)
if (type == LocationMessages.TYPE_USER_MAP || type == LocationMessages.TYPE_BOT_MAP) {
prepareMapMessage(shareInfo, message, isBot, sharingMode)
} else if (type == LocationMessages.TYPE_USER_TEXT || type == LocationMessages.TYPE_BOT_TEXT) {
prepareTextMessage(shareInfo, message, isBot, sharingMode)
} else if (type == LocationMessages.TYPE_USER_BOTH || type == LocationMessages.TYPE_BOT_BOTH) {
prepareMapAndTextMessage(shareInfo, message, isBot, sharingMode)
}
}
if (bufferedMessagesFull) {
checkNetworkType()
}
}
private fun prepareTextMessage(shareInfo: TelegramSettings.ShareChatInfo,message: BufferMessage,isBot:Boolean, sharingMode: String) {
log.debug("prepareTextMessage $message")
shareInfo.collectedMessages++
if (shareInfo.currentTextMessageId == -1L) {
if (shareInfo.pendingTextMessage) {
app.locationMessages.addBufferedMessage(message)
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_TEXT)
} else {
app.telegramHelper.sendNewTextLocation(shareInfo, message)
}
}
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_TEXT)
} else {
if (shareInfo.pendingTdLib < 10) {
app.telegramHelper.editTextLocation(shareInfo, message)
} else {
app.locationMessages.addBufferedMessage(message)
}
}
}
}
private fun prepareMapMessage(shareInfo: TelegramSettings.ShareChatInfo,message: BufferMessage,isBot:Boolean, sharingMode: String) {
log.debug("prepareMapMessage $message")
shareInfo.collectedMessages++
if (shareInfo.currentMapMessageId == -1L) {
if (shareInfo.pendingMapMessage) {
app.locationMessages.addBufferedMessage(message)
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP)
} else {
app.telegramHelper.sendNewMapLocation(shareInfo, message)
}
}
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP)
} else {
if (shareInfo.pendingTdLib < 10) {
app.telegramHelper.editMapLocation(shareInfo, message)
} else {
shareInfo.collectedMessages++
app.locationMessages.addBufferedMessage(message)
}
}
}
}
private fun prepareMapAndTextMessage(shareInfo: TelegramSettings.ShareChatInfo, message: BufferMessage, isBot:Boolean, sharingMode: String) {
log.debug("prepareMapAndTextMessage $message")
shareInfo.collectedMessages++
if (shareInfo.pendingMapMessage || shareInfo.pendingTextMessage || shareInfo.pendingTdLib >= 10) {
app.locationMessages.addBufferedMessage(message)
} else {
if (isBot) {
sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP_AND_TEXT)
} else {
if (shareInfo.currentMapMessageId == -1L) {
app.telegramHelper.sendNewMapLocation(shareInfo, message)
} else {
app.telegramHelper.editMapLocation(shareInfo, message)
}
if (shareInfo.currentTextMessageId == -1L) {
app.telegramHelper.sendNewTextLocation(shareInfo, message)
} else {
app.telegramHelper.editTextLocation(shareInfo, message)
}
}
}
}
private fun checkNetworkType(){
if (app.isInternetConnectionAvailable) {
val networkType = when {
app.isWifiConnected -> TdApi.NetworkTypeWiFi()
app.isMobileConnected -> TdApi.NetworkTypeMobile()
else -> TdApi.NetworkTypeOther()
}
app.telegramHelper.networkChange(networkType)
}
}
private fun sendLocationToBot(locationMessage: BufferMessage, sharingMode: String, shareInfo: TelegramSettings.ShareChatInfo, shareType: String) {
if (app.isInternetConnectionAvailable) {
log.debug("sendLocationToBot $locationMessage")
val url = getDeviceSharingUrl(locationMessage, sharingMode)
AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false,
object : AndroidNetworkUtils.OnRequestResultListener {
override fun onResult(result: String?) {
val chatsShareInfo = app.settings.getChatsShareInfo()
val success = checkResultAndUpdateShareInfoSuccessfulSendTime(result, chatsShareInfo)
val osmandBotId = app.telegramHelper.getOsmandBot()?.id ?: -1
val device = app.settings.getCurrentSharingDevice()
if (success && shareInfo.shouldSendViaBotMessage && osmandBotId != -1 && device != null) {
app.telegramHelper.sendViaBotLocationMessage(osmandBotId, shareInfo, TdApi.Location(locationMessage.lat, locationMessage.lon), device, shareType)
shareInfo.shouldSendViaBotMessage = false
}
}
})
}
}
private fun getDeviceSharingUrl(loc: BufferMessage, sharingMode: String): String {
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.lat}&lon=${loc.lon}"
val builder = StringBuilder(url)
if (loc.hasBearing() && loc.bearing != 0.0f) {
if (loc.bearing != 0.0) {
builder.append("&azi=${loc.bearing}")
}
if (loc.hasSpeed() && loc.speed != 0.0f) {
if (loc.speed != 0.0) {
builder.append("&spd=${loc.speed}")
}
if (loc.hasAltitude() && loc.altitude != 0.0) {
if (loc.altitude != 0.0) {
builder.append("&alt=${loc.altitude}")
}
if (loc.hasAccuracy() && loc.accuracy != 0.0f) {
builder.append("&hdop=${loc.accuracy}")
if (loc.hdop != 0.0) {
builder.append("&hdop=${loc.hdop}")
}
return builder.toString()
}
private fun updateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>) {
private fun checkResultAndUpdateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>):Boolean {
if (result != null) {
try {
val jsonResult = JSONObject(result)
@ -186,22 +360,12 @@ class ShareLocationHelper(private val app: TelegramApplication) {
chatsShareInfo.forEach { (_, shareInfo) ->
shareInfo.lastSuccessfulSendTimeMs = currentTime
}
return true
}
} catch (e: JSONException) {
}
}
}
private fun checkAndSendViaBotMessages(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: TdApi.Location, osmandBot: TdApi.User) {
val device = app.settings.getCurrentSharingDevice()
if (device != null) {
chatsShareInfo.forEach { (_, shareInfo) ->
if (shareInfo.shouldSendViaBotMessage) {
app.telegramHelper.sendViaBotLocationMessage(osmandBot.id, shareInfo, location, device,app.settings.shareTypeValue)
shareInfo.shouldSendViaBotMessage = false
}
}
}
return false
}
private fun refreshNotification() {

View file

@ -8,10 +8,11 @@ import net.osmand.aidl.map.ALatLon
import net.osmand.aidl.maplayer.point.AMapPoint
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation
import net.osmand.telegram.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
import org.drinkless.td.libcore.telegram.TdApi
import java.io.File
import java.util.concurrent.Executors
@ -62,7 +63,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
execOsmandApi {
val messages = telegramHelper.getMessages()
for (message in messages) {
val date = telegramHelper.getLastUpdatedTime(message)
val date = OsmandLocationUtils.getLastUpdatedTime(message)
val messageShowingTime = System.currentTimeMillis() / 1000 - date
if (messageShowingTime > app.settings.locHistoryTime) {
removeMapPoint(message.chatId, message)
@ -78,9 +79,9 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val chatId = message.chatId
val chatTitle = telegramHelper.getChat(message.chatId)?.title
val content = message.content
val date = telegramHelper.getLastUpdatedTime(message)
val date = OsmandLocationUtils.getLastUpdatedTime(message)
val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime
if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserTextLocation && content.isValid()))) {
if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserLocation && content.isValid()))) {
var userName = ""
var photoPath: String? = null
val user = telegramHelper.getUser(message.senderUserId)
@ -105,7 +106,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val params = generatePointParams(photoPath, stale)
val aLatLon = when (content) {
is TdApi.MessageLocation -> ALatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> ALatLon(content.lat, content.lon)
is MessageUserLocation -> ALatLon(content.lat, content.lon)
else -> null
}
if (aLatLon != null) {
@ -250,7 +251,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
private fun removeMapPoint(chatId: Long, message: TdApi.Message) {
val content = message.content
if (content is TdApi.MessageLocation || content is MessageUserTextLocation) {
if (content is TdApi.MessageLocation || content is MessageUserLocation) {
osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}")
} else if (content is MessageOsmAndBotLocation) {
osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${content.name}")

View file

@ -1,23 +1,25 @@
package net.osmand.telegram.helpers
import android.text.TextUtils
import net.osmand.Location
import net.osmand.PlatformUtil
import net.osmand.telegram.SHARE_TYPE_MAP
import net.osmand.telegram.SHARE_TYPE_MAP_AND_TEXT
import net.osmand.telegram.SHARE_TYPE_TEXT
import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.*
import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.*
import net.osmand.telegram.utils.BASE_SHARING_URL
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_EXT
import net.osmand.util.GeoPointParserUtil
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.OsmandLocationUtils.DEVICE_PREFIX
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation
import net.osmand.telegram.utils.OsmandLocationUtils.USER_TEXT_LOCATION_TITLE
import net.osmand.telegram.utils.OsmandLocationUtils.getLastUpdatedTime
import net.osmand.telegram.utils.OsmandLocationUtils.parseOsmAndBotLocation
import net.osmand.telegram.utils.OsmandLocationUtils.parseOsmAndBotLocationContent
import net.osmand.telegram.utils.OsmandLocationUtils.parseTextLocation
import org.drinkless.td.libcore.telegram.Client
import org.drinkless.td.libcore.telegram.Client.ResultHandler
import org.drinkless.td.libcore.telegram.TdApi
import org.drinkless.td.libcore.telegram.TdApi.AuthorizationState
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
@ -36,32 +38,6 @@ class TelegramHelper private constructor() {
private const val IGNORED_ERROR_CODE = 406
private const val MESSAGE_CANNOT_BE_EDITED_ERROR_CODE = 5
private const val DEVICE_PREFIX = "Device: "
private const val LOCATION_PREFIX = "Location: "
private const val LAST_LOCATION_PREFIX = "Last location: "
private const val UPDATED_PREFIX = "Updated: "
private const val USER_TEXT_LOCATION_TITLE = "\uD83D\uDDFA OsmAnd sharing:"
private const val SHARING_LINK = "https://play.google.com/store/apps/details?id=net.osmand.telegram"
private const val ALTITUDE_PREFIX = "Altitude: "
private const val SPEED_PREFIX = "Speed: "
private const val HDOP_PREFIX = "Horizontal precision: "
private const val NOW = "now"
private const val FEW_SECONDS_AGO = "few seconds ago"
private const val SECONDS_AGO_SUFFIX = " seconds ago"
private const val MINUTES_AGO_SUFFIX = " minutes ago"
private const val HOURS_AGO_SUFFIX = " hours ago"
private const val UTC_FORMAT_SUFFIX = " UTC"
private val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
private val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
// min and max values for the Telegram API
const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61
const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day
@ -172,6 +148,8 @@ class TelegramHelper private constructor() {
fun getCurrentUser() = currentUser
fun getCurrentUserId() = currentUser?.id ?: -1
fun getUserMessage(user: TdApi.User) =
usersLocationMessages.values.firstOrNull { it.senderUserId == user.id }
@ -216,15 +194,6 @@ class TelegramHelper private constructor() {
return chat.type is TdApi.ChatTypeSupergroup || chat.type is TdApi.ChatTypeBasicGroup
}
fun getLastUpdatedTime(message: TdApi.Message): Int {
val content = message.content
return when (content) {
is MessageOsmAndBotLocation -> content.lastUpdated
is MessageUserTextLocation -> content.lastUpdated
else -> Math.max(message.editDate, message.date)
}
}
fun isPrivateChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypePrivate
fun isSecretChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypeSecret
@ -324,7 +293,7 @@ class TelegramHelper private constructor() {
try {
log.debug("Loading native tdlib...")
System.loadLibrary("tdjni")
Client.setLogVerbosityLevel(0)
Client.setLogVerbosityLevel(1)
libraryLoaded = true
} catch (e: Throwable) {
log.error("Failed to load tdlib", e)
@ -349,6 +318,12 @@ class TelegramHelper private constructor() {
}
}
fun networkChange(networkType: TdApi.NetworkType) {
client?.send(TdApi.SetNetworkType(networkType)) { obj ->
log.debug(obj)
}
}
fun isInit() = client != null && haveAuthorization
fun getUserPhotoPath(user: TdApi.User?) = when {
@ -421,7 +396,7 @@ class TelegramHelper private constructor() {
private fun isUserLocationMessage(message: TdApi.Message): Boolean {
val cont = message.content
return (cont is MessageUserTextLocation || cont is TdApi.MessageLocation)
return (cont is MessageUserLocation || cont is TdApi.MessageLocation)
}
private fun hasLocalUserPhoto(user: TdApi.User): Boolean {
@ -713,10 +688,6 @@ class TelegramHelper private constructor() {
}
}
fun loadMessage(chatId: Long, messageId: Long) {
requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage)
}
private fun requestMessage(chatId: Long, messageId: Long, onComplete: (TdApi.Message) -> Unit) {
client?.send(TdApi.GetMessage(chatId, messageId)) { obj ->
if (obj is TdApi.Message) {
@ -728,7 +699,10 @@ class TelegramHelper private constructor() {
private fun addNewMessage(message: TdApi.Message) {
lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate))
if (message.isAppropriate()) {
log.debug("addNewMessage: $message")
if (message.isOutgoing) {
return
}
log.debug("addNewMessage: ${message.id}")
val fromBot = isOsmAndBot(message.senderUserId)
val viaBot = isOsmAndBot(message.viaBotUserId)
val oldContent = message.content
@ -741,11 +715,6 @@ class TelegramHelper private constructor() {
} else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) {
message.content = parseOsmAndBotLocation(message)
}
if (message.isOutgoing) {
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(message))
}
} else {
removeOldMessages(message, fromBot, viaBot)
val oldMessage = usersLocationMessages.values.firstOrNull { getSenderMessageId(it) == getSenderMessageId(message) && !fromBot && !viaBot }
val hasNewerMessage = oldMessage != null && (Math.max(message.editDate, message.date) < Math.max(oldMessage.editDate, oldMessage.date))
@ -753,13 +722,12 @@ class TelegramHelper private constructor() {
usersLocationMessages[message.id] = message
}
incomingMessagesListeners.forEach {
if (!hasNewerMessage || it is SavingTracksDbHelper) {
if (!hasNewerMessage || it is TelegramService) {
it.onReceiveChatLocationMessages(message.chatId, message)
}
}
}
}
}
private fun removeOldMessages(newMessage: TdApi.Message, fromBot: Boolean, viaBot: Boolean) {
val iterator = usersLocationMessages.entries.iterator()
@ -779,34 +747,13 @@ class TelegramHelper private constructor() {
}
}
} else if (sameSender && isUserLocationMessage(message) && isUserLocationMessage(newMessage)
&& Math.max(newMessage.editDate, newMessage.date) > Math.max(message.editDate, message.date)) {
&& Math.max(newMessage.editDate, newMessage.date) >= Math.max(message.editDate, message.date)) {
iterator.remove()
}
}
}
}
/**
* @chatId Id of the chat
* @livePeriod Period for which the location can be updated, in seconds; should be between 60 and 86400 for a live location and 0 otherwise.
* @latitude Latitude of the location
* @longitude Longitude of the location
*/
fun sendLiveLocationMessage(chatsShareInfo:Map<Long, TelegramSettings.ShareChatInfo>, latitude: Double, longitude: Double): Boolean {
if (!requestingActiveLiveLocationMessages && haveAuthorization) {
if (needRefreshActiveLiveLocationMessages) {
getActiveLiveLocationMessages {
sendLiveLocationImpl(chatsShareInfo, latitude, longitude)
}
needRefreshActiveLiveLocationMessages = false
} else {
sendLiveLocationImpl(chatsShareInfo, latitude, longitude)
}
return true
}
return false
}
fun stopSendingLiveLocationToChat(shareInfo: TelegramSettings.ShareChatInfo) {
if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) {
client?.send(
@ -854,10 +801,7 @@ class TelegramHelper private constructor() {
}
}
private fun recreateLiveLocationMessage(
shareInfo: TelegramSettings.ShareChatInfo,
content: TdApi.InputMessageContent
) {
private fun recreateLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) {
if (shareInfo.chatId != -1L) {
val array = LongArray(1)
if (content is TdApi.InputMessageLocation) {
@ -889,18 +833,42 @@ class TelegramHelper private constructor() {
private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) {
needRefreshActiveLiveLocationMessages = true
log.debug("sendNewLiveLocationMessage")
client?.send(
TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
if (content is TdApi.InputMessageText) {
handleTextLocationMessageUpdate(obj, shareInfo)
} else if (content is TdApi.InputMessageLocation) {
handleMapLocationMessageUpdate(obj, shareInfo)
}
}
private fun sendLiveLocationImpl(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, latitude: Double, longitude: Double) {
val location = TdApi.Location(latitude, longitude)
chatsShareInfo.forEach { (chatId, shareInfo) ->
if (shareInfo.getChatLiveMessageExpireTime() <= 0) {
return@forEach
}
fun sendNewTextLocation(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.BufferMessage) {
shareInfo.updateTextMessageId = 1
val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location)
if (!shareInfo.pendingTextMessage) {
shareInfo.pendingTextMessage = true
shareInfo.pendingTdLib++
log.error("sendNewTextLocation ${shareInfo.pendingTdLib}")
client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
handleTextLocationMessageUpdate(obj, shareInfo)
}
}
}
fun editTextLocation(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.BufferMessage) {
val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location)
if (shareInfo.currentTextMessageId!=-1L) {
shareInfo.pendingTdLib++
log.info("editTextLocation ${shareInfo.currentTextMessageId} pendingTdLib: ${shareInfo.pendingTdLib}")
client?.send(TdApi.EditMessageText(shareInfo.chatId, shareInfo.currentTextMessageId, null, content)) { obj ->
handleTextLocationMessageUpdate(obj, shareInfo)
}
}
}
fun sendNewMapLocation(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.BufferMessage) {
needRefreshActiveLiveLocationMessages = true
val location = TdApi.Location(locationMessage.lat, locationMessage.lon)
val livePeriod =
if (shareInfo.currentMessageLimit > (shareInfo.start + MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC)) {
MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC
@ -908,52 +876,24 @@ class TelegramHelper private constructor() {
shareInfo.livePeriod.toInt()
}
val content = TdApi.InputMessageLocation(location, livePeriod)
val msgId = shareInfo.currentMapMessageId
val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendMapMessageTime)
log.debug("sendLiveLocationImpl - $msgId pendingMapMessage ${shareInfo.pendingMapMessage}")
if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousMapMessage) {
recreateLiveLocationMessage(shareInfo, content)
shareInfo.shouldDeletePreviousMapMessage = false
shareInfo.currentMapMessageId = -1
} else {
log.debug("EditMessageLiveLocation - $msgId")
client?.send(
TdApi.EditMessageLiveLocation(chatId, msgId, null, location)) { obj ->
if (!shareInfo.pendingMapMessage) {
shareInfo.pendingMapMessage = true
shareInfo.pendingTdLib++
log.error("sendNewMapLocation ${shareInfo.pendingTdLib}")
client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj ->
handleMapLocationMessageUpdate(obj, shareInfo)
}
}
} else if (!shareInfo.pendingMapMessage || shareInfo.pendingMapMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) {
sendNewLiveLocationMessage(shareInfo, content)
}
}
}
fun sendLiveLocationText(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: Location) {
chatsShareInfo.forEach { (chatId, shareInfo) ->
if (shareInfo.getChatLiveMessageExpireTime() <= 0) {
return@forEach
}
val msgId = shareInfo.currentTextMessageId
if (msgId == -1L) {
shareInfo.updateTextMessageId = 1
}
val content = getTextMessageContent(shareInfo.updateTextMessageId, location)
val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendTextMessageTime)
log.debug("sendLiveLocationText - $msgId pendingMapMessage ${shareInfo.pendingTextMessage}")
if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousTextMessage) {
recreateLiveLocationMessage(shareInfo, content)
shareInfo.shouldDeletePreviousTextMessage = false
} else {
client?.send(TdApi.EditMessageText(chatId, msgId, null, content)) { obj ->
handleTextLocationMessageUpdate(obj, shareInfo)
}
}
} else if (!shareInfo.pendingTextMessage || shareInfo.pendingTextMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) {
client?.send(TdApi.SendMessage(chatId, 0, false, false, null, content)) { obj ->
handleTextLocationMessageUpdate(obj, shareInfo)
}
fun editMapLocation(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.BufferMessage) {
needRefreshActiveLiveLocationMessages = true
val location = TdApi.Location(locationMessage.lat, locationMessage.lon)
if (shareInfo.currentMapMessageId!=-1L) {
shareInfo.pendingTdLib++
log.info("editMapLocation ${shareInfo.currentMapMessageId} pendingTdLib: ${shareInfo.pendingTdLib}")
client?.send(TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, location)) { obj ->
handleMapLocationMessageUpdate(obj, shareInfo)
}
}
}
@ -961,6 +901,7 @@ class TelegramHelper private constructor() {
private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) {
when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> {
log.debug("handleMapLocationMessageUpdate - ERROR $obj")
val error = obj as TdApi.Error
needRefreshActiveLiveLocationMessages = true
if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) {
@ -978,6 +919,8 @@ class TelegramHelper private constructor() {
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
shareInfo.hasSharingError = true
needRefreshActiveLiveLocationMessages = true
shareInfo.pendingMapMessage = false
log.debug("handleTextLocationMessageUpdate - MessageSendingStateFailed")
outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(-1, "Map location message ${obj.id} failed to send")
}
@ -986,9 +929,14 @@ class TelegramHelper private constructor() {
shareInfo.pendingMapMessage = true
shareInfo.lastSendMapMessageTime = obj.date
log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending")
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj))
}
}
else -> {
shareInfo.hasSharingError = false
shareInfo.pendingMapMessage = false
log.debug("handleMapLocationMessageUpdate - MessageSendingStateSuccess")
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj))
}
@ -1002,6 +950,7 @@ class TelegramHelper private constructor() {
private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) {
when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> {
log.debug("handleTextLocationMessageUpdate - ERROR")
val error = obj as TdApi.Error
if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) {
shareInfo.shouldDeletePreviousTextMessage = true
@ -1017,7 +966,9 @@ class TelegramHelper private constructor() {
when {
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
shareInfo.hasSharingError = true
shareInfo.pendingTextMessage = false
needRefreshActiveLiveLocationMessages = true
log.debug("handleTextLocationMessageUpdate - MessageSendingStateFailed")
outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(-1, "Text location message ${obj.id} failed to send")
}
@ -1026,9 +977,14 @@ class TelegramHelper private constructor() {
shareInfo.pendingTextMessage = true
shareInfo.lastSendTextMessageTime = obj.date
log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending")
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj))
}
}
else -> {
shareInfo.hasSharingError = false
shareInfo.pendingTextMessage = false
log.debug("handleTextLocationMessageUpdate - MessageSendingStateSuccess")
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj))
}
@ -1039,54 +995,6 @@ class TelegramHelper private constructor() {
}
}
private fun formatLocation(sig: Location): String {
return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude)
}
private fun formatFullTime(ti: Long): String {
val dt = Date(ti)
return UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + " UTC"
}
private fun getTextMessageContent(updateId: Int, location: Location): TdApi.InputMessageText {
val entities = mutableListOf<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.latitude}&lon=${location.longitude}")))
builder.append("$locationMessage\n")
if (location.hasAltitude() && location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.hasSpeed() && location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hasAccuracy() && location.accuracy != 0.0f && location.speed == 0.0f) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.accuracy.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
}
fun logout(): Boolean {
return if (libraryLoaded) {
haveAuthorization = false
@ -1215,188 +1123,6 @@ class TelegramHelper private constructor() {
}
}
private fun parseOsmAndBotLocation(message: TdApi.Message): MessageOsmAndBotLocation {
val messageLocation = message.content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = getOsmAndBotDeviceName(message)
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = getLastUpdatedTime(message)
}
}
private fun parseOsmAndBotLocationContent(oldContent:MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation {
val messageLocation = content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = oldContent.name
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = (System.currentTimeMillis() / 1000).toInt()
}
}
private fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean = true): MessageLocation {
val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserTextLocation()
var locationNA = false
for (s in text.text.lines()) {
when {
s.startsWith(DEVICE_PREFIX) -> {
if (res is MessageOsmAndBotLocation) {
res.name = s.removePrefix(DEVICE_PREFIX)
}
}
s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> {
var locStr: String
var parse = true
if (s.startsWith(LAST_LOCATION_PREFIX)) {
locStr = s.removePrefix(LAST_LOCATION_PREFIX)
if (!locationNA) {
parse = false
}
} else {
locStr = s.removePrefix(LOCATION_PREFIX)
if (locStr.trim() == "n/a") {
locationNA = true
parse = false
}
}
if (parse) {
try {
val urlTextEntity = text.entities.firstOrNull { it.type is TdApi.TextEntityTypeTextUrl }
if (urlTextEntity != null && urlTextEntity.offset == text.text.indexOf(locStr)) {
val url = (urlTextEntity.type as TdApi.TextEntityTypeTextUrl).url
val point: GeoPointParserUtil.GeoParsedPoint? = GeoPointParserUtil.parse(url)
if (point != null) {
res.lat = point.latitude
res.lon = point.longitude
}
} else {
val (latS, lonS) = locStr.split(" ")
res.lat = latS.dropLast(1).toDouble()
res.lon = lonS.toDouble()
val timeIndex = locStr.indexOf("(")
if (timeIndex != -1) {
val updatedS = locStr.substring(timeIndex, locStr.length)
res.lastUpdated = (parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
s.startsWith(ALTITUDE_PREFIX) -> {
val altStr = s.removePrefix(ALTITUDE_PREFIX)
try {
val alt = altStr.split(" ").first()
res.altitude = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(SPEED_PREFIX) -> {
val altStr = s.removePrefix(SPEED_PREFIX)
try {
val alt = altStr.split(" ").first()
res.speed = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(HDOP_PREFIX) -> {
val altStr = s.removePrefix(HDOP_PREFIX)
try {
val alt = altStr.split(" ").first()
res.hdop = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(UPDATED_PREFIX) -> {
if (res.lastUpdated == 0) {
val updatedStr = s.removePrefix(UPDATED_PREFIX)
val endIndex = updatedStr.indexOf("(")
val updatedS = updatedStr.substring(0, if (endIndex != -1) endIndex else updatedStr.length)
val parsedTime = (parseTime(updatedS.trim()) / 1000).toInt()
val currentTime = (System.currentTimeMillis() / 1000) - 1
res.lastUpdated = if (parsedTime < currentTime) parsedTime else currentTime.toInt()
}
}
}
}
return res
}
private fun parseTime(timeS: String): Long {
try {
when {
timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000
timeS.endsWith(SECONDS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX)
return System.currentTimeMillis() - locStr.toLong() * 1000
}
timeS.endsWith(MINUTES_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX)
val minutes = locStr.toLong()
return System.currentTimeMillis() - minutes * 60 * 1000
}
timeS.endsWith(HOURS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX)
val hours = locStr.toLong()
return (System.currentTimeMillis() - hours * 60 * 60 * 1000)
}
timeS.endsWith(UTC_FORMAT_SUFFIX) -> {
val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX)
val (latS, lonS) = locStr.split(" ")
val date = UTC_DATE_FORMAT.parse(latS)
val time = UTC_TIME_FORMAT.parse(lonS)
val res = date.time + time.time
return res
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return 0
}
abstract class MessageLocation : TdApi.MessageContent() {
var lat: Double = Double.NaN
internal set
var lon: Double = Double.NaN
internal set
var lastUpdated: Int = 0
internal set
var speed: Double = 0.0
internal set
var altitude: Double = 0.0
internal set
var hdop: Double = 0.0
internal set
override fun getConstructor() = -1
abstract fun isValid(): Boolean
}
class MessageOsmAndBotLocation : MessageLocation() {
var name: String = ""
internal set
override fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN
}
class MessageUserTextLocation : MessageLocation() {
override fun isValid() = lat != Double.NaN && lon != Double.NaN
}
class OrderedChat internal constructor(internal val order: Long, internal val chatId: Long, internal val isChannel: Boolean) : Comparable<OrderedChat> {
override fun compareTo(other: OrderedChat): Int {
@ -1583,26 +1309,10 @@ class TelegramHelper private constructor() {
}
}
}
TdApi.UpdateMessageEdited.CONSTRUCTOR -> {
val updateMessageEdited = obj as TdApi.UpdateMessageEdited
val message = usersLocationMessages[updateMessageEdited.messageId]
if (message == null) {
updateMessageEdited.apply {
requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage)
}
} else {
synchronized(message) {
message.editDate = updateMessageEdited.editDate
lastTelegramUpdateTime = Math.max(message.date, message.editDate)
}
incomingMessagesListeners.forEach {
it.updateLocationMessages()
}
}
}
TdApi.UpdateMessageContent.CONSTRUCTOR -> {
val updateMessageContent = obj as TdApi.UpdateMessageContent
val message = usersLocationMessages[updateMessageContent.messageId]
log.debug("UpdateMessageContent " + updateMessageContent.messageId)
if (message == null) {
updateMessageContent.apply {
requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage)
@ -1622,7 +1332,6 @@ class TelegramHelper private constructor() {
newContent
}
}
log.debug("UpdateMessageContent " + message.senderUserId)
incomingMessagesListeners.forEach {
it.onReceiveChatLocationMessages(message.chatId, message)
}
@ -1630,6 +1339,7 @@ class TelegramHelper private constructor() {
}
TdApi.UpdateNewMessage.CONSTRUCTOR -> {
addNewMessage((obj as TdApi.UpdateNewMessage).message)
log.debug("UpdateNewMessage " + obj.message.id)
}
TdApi.UpdateMessageMentionRead.CONSTRUCTOR -> {
val updateChat = obj as TdApi.UpdateMessageMentionRead
@ -1736,9 +1446,9 @@ class TelegramHelper private constructor() {
}
}
TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> {
val udateMessageSendSucceeded = obj as TdApi.UpdateMessageSendSucceeded
val message = udateMessageSendSucceeded.message
log.debug("UpdateMessageSendSucceeded: $message")
val updateSucceeded = obj as TdApi.UpdateMessageSendSucceeded
val message = updateSucceeded.message
log.debug("UpdateMessageSendSucceeded: ${message.id} oldId: ${updateSucceeded.oldMessageId}")
outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(message))
}

View file

@ -6,9 +6,10 @@ import android.widget.ImageView
import net.osmand.data.LatLon
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation
import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.GPXUtilities.GPXFile
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation
import org.drinkless.td.libcore.telegram.TdApi
object TelegramUiHelper {
@ -65,11 +66,11 @@ object TelegramUiHelper {
val user = helper.getUser(userId)
val message = messages.firstOrNull { it.viaBotUserId == 0 }
if (message != null) {
res.lastUpdated = helper.getLastUpdatedTime(message)
res.lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
val content = message.content
if (content is TdApi.MessageLocation) {
res.latLon = LatLon(content.location.latitude, content.location.longitude)
} else if (content is MessageUserTextLocation) {
} else if (content is MessageUserLocation) {
res.latLon = LatLon(content.lat, content.lon)
}
}
@ -110,7 +111,7 @@ object TelegramUiHelper {
val content = message.content
return when (content) {
is MessageOsmAndBotLocation -> botMessageToLocationItem(chat, content)
is MessageUserTextLocation -> locationMessageToLocationItem(helper, chat, message)
is MessageUserLocation -> locationMessageToLocationItem(helper, chat, message)
is TdApi.MessageLocation -> locationMessageToLocationItem(helper, chat, message)
else -> null
}
@ -125,12 +126,12 @@ object TelegramUiHelper {
return when (content) {
is MessageOsmAndBotLocation -> botMessageToChatItem(helper, chat, content)
is TdApi.MessageLocation -> locationMessageToChatItem(helper, chat, message)
is MessageUserTextLocation -> locationMessageToChatItem(helper, chat, message)
is MessageUserLocation -> locationMessageToChatItem(helper, chat, message)
else -> null
}
}
fun gpxToChatItem(helper: TelegramHelper, gpx: GPXUtilities.GPXFile, simpleUserItem: Boolean): GpxChatItem? {
fun gpxToChatItem(helper: TelegramHelper, gpx: GPXFile, simpleUserItem: Boolean): GpxChatItem? {
return if (simpleUserItem) gpxToUserGpxChatItem(helper, gpx) else gpxToGpxChatItem(helper, gpx)
}
@ -165,14 +166,14 @@ object TelegramUiHelper {
name = TelegramUiHelper.getUserName(user)
latLon = when (content) {
is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> LatLon(content.lat, content.lon)
is MessageUserLocation -> LatLon(content.lat, content.lon)
else -> null
}
photoPath = helper.getUserPhotoPath(user)
grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture
userId = message.senderUserId
lastUpdated = helper.getLastUpdatedTime(message)
lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
}
}
@ -211,7 +212,7 @@ object TelegramUiHelper {
name = TelegramUiHelper.getUserName(user)
latLon = when (content) {
is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude)
is MessageUserTextLocation -> LatLon(content.lat, content.lon)
is MessageUserLocation -> LatLon(content.lat, content.lon)
else -> null
}
if (helper.isGroup(chat)) {
@ -225,13 +226,13 @@ object TelegramUiHelper {
userId = message.senderUserId
privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat)
chatWithBot = helper.isBot(userId)
lastUpdated = helper.getLastUpdatedTime(message)
lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
}
}
private fun gpxToGpxChatItem(
helper: TelegramHelper,
gpx: GPXUtilities.GPXFile
gpx: GPXFile
): GpxChatItem? {
val user = helper.getUser(gpx.userId) ?: return null
val chat = helper.getChat(gpx.chatId) ?: return null
@ -255,9 +256,36 @@ object TelegramUiHelper {
}
}
fun locationMessagesToChatItem(
helper: TelegramHelper,
messages: List<LocationMessages.LocationMessage>
): LocationMessagesChatItem? {
val message = messages.firstOrNull()
val user = helper.getUser(message?.userId ?: -1) ?: return null
val chat = helper.getChat(message?.chatId ?: -1) ?: return null
return LocationMessagesChatItem().apply {
chatId = chat.id
chatTitle = chat.title
locationMessages = messages
name = TelegramUiHelper.getUserName(user)
if (helper.isGroup(chat)) {
photoPath = helper.getUserPhotoPath(user)
groupPhotoPath = chat.photo?.small?.local?.path
} else {
photoPath = user.profilePhoto?.small?.local?.path
}
grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture
userId = user.id
privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat)
chatWithBot = helper.isBot(userId)
lastUpdated = (messages.maxBy { it.time }?.time ?: -1).toInt()
}
}
private fun gpxToUserGpxChatItem(
helper: TelegramHelper,
gpx: GPXUtilities.GPXFile
gpx: GPXFile
): GpxChatItem? {
val user = helper.getUser(gpx.userId) ?: return null
return GpxChatItem().apply {
@ -322,7 +350,25 @@ object TelegramUiHelper {
class GpxChatItem : ListItem() {
var gpxFile: GPXUtilities.GPXFile? = null
var gpxFile: GPXFile? = null
internal set
var groupPhotoPath: String? = null
internal set
var privateChat: Boolean = false
internal set
var chatWithBot: Boolean = false
internal set
override fun canBeOpenedOnMap() = latLon != null
override fun getMapPointId() = "${chatId}_$userId"
override fun getVisibleName() = chatTitle
}
class LocationMessagesChatItem : ListItem() {
var locationMessages: List<LocationMessages.LocationMessage> = emptyList()
internal set
var groupPhotoPath: String? = null
internal set

View file

@ -19,7 +19,6 @@ import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener
import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener
import net.osmand.telegram.TelegramSettings
import net.osmand.telegram.helpers.SavingTracksDbHelper
import net.osmand.telegram.helpers.TelegramHelper.*
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.helpers.TelegramUiHelper.ChatItem

View file

@ -19,6 +19,8 @@ import android.widget.*
import net.osmand.PlatformUtil
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.LocationMessages
import net.osmand.telegram.helpers.LocationMessages.LocationMessage
import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.helpers.TelegramHelper.*
@ -183,7 +185,6 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
override fun onStop() {
super.onStop()
settings.save()
app.messagesDbHelper.saveMessages()
}
override fun onDestroy() {
@ -291,6 +292,11 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
if (!app.showLocationHelper.showingLocation && settings.hasAnyChatToShowOnMap()) {
app.showLocationHelper.startShowingLocation()
}
if (app.telegramService == null) {
messages.forEach {
app.locationMessages.addNewLocationMessage(it)
}
}
}
override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {}
@ -341,7 +347,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene
fun logoutTelegram(silent: Boolean = false) {
if (telegramHelper.getTelegramAuthorizationState() == TelegramHelper.TelegramAuthorizationState.READY) {
if (app.isInternetConnectionAvailable) {
app.messagesDbHelper.clearMessages()
app.locationMessages.clearBufferedMessages()
settings.clear()
telegramHelper.logout()
} else {

View file

@ -231,7 +231,6 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
override fun onResume() {
super.onResume()
updateCurrentUserPhoto()
telegramHelper.getActiveLiveLocationMessages(null)
updateContent()
updateEnable = true
startHandler()
@ -599,6 +598,9 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
}
holder.title?.text = title
holder.icon?.setOnClickListener {
app.forceUpdateMyLocation()
}
if (holder is ChatViewHolder) {
holder.description?.visibility = View.GONE
if (live) {
@ -714,6 +716,17 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
OsmandFormatter.getFormattedDuration(context!!, expiresIn, true)
)})"
}
holder.gpsPointsCollected?.apply {
if (shareInfo != null) {
val bufferedMessages = shareInfo.pendingTdLib + app.locationMessages.getBufferedMessagesForChat(shareInfo.chatId).size
text = "$bufferedMessages"
}
}
holder.gpsPointsSent?.apply {
if (shareInfo != null) {
text = "${shareInfo.sentMessages}"
}
}
}
}
@ -751,6 +764,8 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
val stopSharingDescr: TextView? = view.findViewById(R.id.stop_in)
val stopSharingFirstPart: TextView? = view.findViewById(R.id.ending_in_first_part)
val stopSharingSecondPart: TextView? = view.findViewById(R.id.ending_in_second_part)
val gpsPointsCollected: TextView? = view.findViewById(R.id.gps_points_collected)
val gpsPointsSent: TextView? = view.findViewById(R.id.gps_points_sent)
}
}

View file

@ -21,6 +21,7 @@ import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.ui.SetTimeDialogFragment.SetTimeListAdapter.ChatViewHolder
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.utils.UiUtils
import net.osmand.util.MapUtils
import org.drinkless.td.libcore.telegram.TdApi
@ -328,7 +329,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te
val message = telegramHelper.getChatMessages(itemId).firstOrNull()
val content = message?.content
if (message != null && content is TdApi.MessageLocation && (location != null && content.location != null)) {
val lastUpdated = telegramHelper.getLastUpdatedTime(message)
val lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
holder.description?.visibility = View.VISIBLE
holder.description?.text = OsmandFormatter.getListItemLiveTimeDescr(app, lastUpdated)

View file

@ -4,6 +4,7 @@ import android.app.DatePickerDialog
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.support.annotation.DrawableRes
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
@ -17,11 +18,11 @@ import android.widget.TextView
import net.osmand.PlatformUtil
import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.LocationMessages
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.OsmandFormatter
import java.util.*
@ -38,13 +39,14 @@ class TimelineTabFragment : Fragment() {
private lateinit var adapter: LiveNowListAdapter
private lateinit var dateStartBtn: TextView
private lateinit var dateEndBtn: TextView
private lateinit var dateBtn: TextView
private lateinit var mainView: View
private var start = 0L
private var end = 0L
private var updateEnable: Boolean = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -74,43 +76,50 @@ class TimelineTabFragment : Fragment() {
monitoringTv.setText(if (monitoringEnabled) R.string.monitoring_is_enabled else R.string.monitoring_is_disabled)
}
dateStartBtn = mainView.findViewById<TextView>(R.id.date_start_btn).apply {
dateBtn = mainView.findViewById<TextView>(R.id.date_btn).apply {
setOnClickListener {
selectStartDate()
selectDate()
}
setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_start), null, null, null)
setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light))
}
dateEndBtn = mainView.findViewById<TextView>(R.id.date_end_btn).apply {
setOnClickListener {
selectEndDate()
}
setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_add), null, null, null)
}
setupBtnTextColor(dateStartBtn)
setupBtnTextColor(dateEndBtn)
return mainView
}
private fun setupBtnTextColor(textView: TextView) {
textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light))
override fun onResume() {
super.onResume()
updateEnable = true
startHandler()
}
private fun selectStartDate() {
override fun onPause() {
super.onPause()
updateEnable = false
}
private fun selectDate() {
val dateFromDialog =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val from = Calendar.getInstance()
from.set(Calendar.YEAR, year)
from.set(Calendar.MONTH, monthOfYear)
from.set(Calendar.DAY_OF_MONTH, dayOfMonth)
from.set(Calendar.HOUR_OF_DAY, 0)
from.clear(Calendar.MINUTE)
from.clear(Calendar.SECOND)
from.clear(Calendar.MILLISECOND)
start = from.timeInMillis
from.set(Calendar.HOUR_OF_DAY, 23)
from.set(Calendar.MINUTE, 59)
from.set(Calendar.SECOND, 59)
from.set(Calendar.MILLISECOND, 999)
end = from.timeInMillis
updateList()
updateDateButtons()
updateDateButton()
}
val startCalendar = Calendar.getInstance()
startCalendar.timeInMillis = start
@ -121,34 +130,8 @@ class TimelineTabFragment : Fragment() {
).show()
}
private fun selectEndDate() {
val dateFromDialog =
DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth ->
val from = Calendar.getInstance()
from.set(Calendar.YEAR, year)
from.set(Calendar.MONTH, monthOfYear)
from.set(Calendar.DAY_OF_MONTH, dayOfMonth)
from.set(Calendar.HOUR_OF_DAY, 23)
from.set(Calendar.MINUTE, 59)
from.set(Calendar.SECOND, 59)
from.set(Calendar.MILLISECOND, 999)
end = from.timeInMillis
updateList()
updateDateButtons()
}
val endCalendar = Calendar.getInstance()
endCalendar.timeInMillis = end
DatePickerDialog(context, dateFromDialog,
endCalendar.get(Calendar.YEAR),
endCalendar.get(Calendar.MONTH),
endCalendar.get(Calendar.DAY_OF_MONTH)
).show()
}
private fun updateDateButtons() {
dateStartBtn.text = OsmandFormatter.getFormattedDate(start / 1000)
dateEndBtn.text = OsmandFormatter.getFormattedDate(end / 1000)
dateEndBtn.setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_end), null, null, null)
private fun updateDateButton() {
dateBtn.text = OsmandFormatter.getFormattedDate(start / 1000)
}
private fun getPressedStateIcon(@DrawableRes iconId: Int): Drawable? {
@ -162,32 +145,35 @@ class TimelineTabFragment : Fragment() {
return normal
}
private fun startHandler() {
val updateAdapter = Handler()
updateAdapter.postDelayed({
if (updateEnable) {
updateList()
startHandler()
}
}, ADAPTER_UPDATE_INTERVAL_MIL)
}
private fun updateList() {
val res = mutableListOf<ListItem>()
val s = System.currentTimeMillis()
log.debug("updateList $s")
val ignoredUsersIds = ArrayList<Int>()
val currentUserId = telegramHelper.getCurrentUser()?.id
if (currentUserId != null) {
val currentUserGpx:GPXUtilities.GPXFile? = app.savingTracksDbHelper.collectRecordedDataForUser(currentUserId, 0, start, end)
if (currentUserGpx != null) {
TelegramUiHelper.gpxToChatItem(telegramHelper, currentUserGpx, true)?.also {
res.add(it)
}
}
ignoredUsersIds.add(currentUserId)
}
val gpxFiles = app.savingTracksDbHelper.collectRecordedDataForUsers(start, end, ignoredUsersIds)
val e = System.currentTimeMillis()
gpxFiles.forEach {
TelegramUiHelper.gpxToChatItem(telegramHelper, it,false)?.also { chatItem ->
val outgoingMessages = app.locationMessages.getMessagesForUser(currentUserId, start, end)
TelegramUiHelper.locationMessagesToChatItem(telegramHelper, outgoingMessages)?.also { chatItem ->
res.add(chatItem)
}
val ingoingMessages = app.locationMessages.getIngoingMessages(currentUserId, start, end)
val emm = ingoingMessages.distinctBy { Pair(it.userId, it.chatId) }
emm.forEach { message ->
TelegramUiHelper.locationMessagesToChatItem(telegramHelper,
ingoingMessages.filter { it.chatId == message.chatId && it.userId == message.userId })?.also { chatItem ->
res.add(chatItem)
}
}
}
adapter.items = sortAdapterItems(res)
log.debug("updateList $s dif: ${e - s}")
}
private fun sortAdapterItems(list: MutableList<ListItem>): MutableList<ListItem> {
@ -225,24 +211,17 @@ class TimelineTabFragment : Fragment() {
holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE
holder.lastTelegramUpdateTime?.visibility = View.GONE
if (item is TelegramUiHelper.GpxChatItem) {
val gpx = item.gpxFile
val groupDescrRowVisible = (!item.privateChat || item.chatWithBot) && item.userId != currentUserId
if (groupDescrRowVisible) {
if (item is TelegramUiHelper.LocationMessagesChatItem) {
val distance = OsmandFormatter.getFormattedDistance(getDistance(item.locationMessages),app)
val name = if ((!item.privateChat || item.chatWithBot) && item.userId != currentUserId) item.getVisibleName() else ""
holder.groupDescrContainer?.visibility = View.VISIBLE
holder.groupTitle?.text = item.getVisibleName()
holder.groupTitle?.text = "$distance (${getString(R.string.points_size, item.locationMessages.size)}) $name"
TelegramUiHelper.setupPhoto(app, holder.groupImage, item.groupPhotoPath, item.placeholderId, false)
} else {
holder.groupDescrContainer?.visibility = View.GONE
}
holder.userRow?.setOnClickListener {
if (gpx != null) {
childFragmentManager.also {
UserGpxInfoFragment.showInstance(it, gpx, start, end)
UserGpxInfoFragment.showInstance(it, item.userId, item.chatId, start, end)
}
}
}
holder.imageButton?.visibility = View.GONE
holder.showOnMapRow?.visibility = View.GONE
holder.bottomDivider?.visibility = if (lastItem) View.GONE else View.VISIBLE
@ -250,6 +229,14 @@ class TimelineTabFragment : Fragment() {
}
}
private fun getDistance(messages: List<LocationMessages.LocationMessage>): Float {
var dist = 0.0
messages.forEach {
dist += it.distanceFromPrev
}
return dist.toFloat()
}
override fun getItemCount() = items.size
inner class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
@ -269,4 +256,8 @@ class TimelineTabFragment : Fragment() {
val bottomDivider: View? = view.findViewById(R.id.bottom_divider)
}
}
companion object {
private const val ADAPTER_UPDATE_INTERVAL_MIL = 15 * 1000L // 15 sec
}
}

View file

@ -21,11 +21,11 @@ import net.osmand.PlatformUtil
import net.osmand.aidl.gpx.AGpxBitmap
import net.osmand.telegram.R
import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.SavingTracksDbHelper
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.util.Algorithms
import java.io.File
import java.text.SimpleDateFormat
@ -37,7 +37,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private val uiUtils get() = app.uiUtils
private lateinit var gpxFile: GPXUtilities.GPXFile
private var gpxFile = GPXUtilities.GPXFile()
private lateinit var dateStartBtn: TextView
private lateinit var timeStartBtn: TextView
@ -52,6 +52,9 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private var startCalendar = Calendar.getInstance()
private var endCalendar = Calendar.getInstance()
private var userId = -1
private var chatId = -1L
override fun onCreateView(
inflater: LayoutInflater,
parent: ViewGroup?,
@ -62,9 +65,6 @@ class UserGpxInfoFragment : BaseDialogFragment() {
readFromBundle(savedInstanceState ?: arguments)
val userId = gpxFile.userId
val chatId = gpxFile.chatId
val user = app.telegramHelper.getUser(userId)
if (user != null) {
mainView.findViewById<TextView>(R.id.title).text = TelegramUiHelper.getUserName(user)
@ -80,8 +80,6 @@ class UserGpxInfoFragment : BaseDialogFragment() {
}
})
updateGPXMap()
val backBtn = mainView.findViewById<ImageView>(R.id.back_button)
backBtn.setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_arrow_back))
backBtn.setOnClickListener {
@ -134,12 +132,13 @@ class UserGpxInfoFragment : BaseDialogFragment() {
openGpx(gpx.path)
} else {
saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener {
OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) {
openGpx(path)
}
override fun onSavingGpxError(warnings: MutableList<String>?) {
override fun onSavingGpxError(warnings: List<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
}
})
@ -156,12 +155,12 @@ class UserGpxInfoFragment : BaseDialogFragment() {
(activity as MainActivity).shareGpx(gpx.path)
} else {
saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener {
OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) {
(activity as MainActivity).shareGpx(path)
}
override fun onSavingGpxError(warnings: MutableList<String>?) {
override fun onSavingGpxError(warnings: List<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
}
})
@ -169,6 +168,8 @@ class UserGpxInfoFragment : BaseDialogFragment() {
}
}
updateGpxInfo()
return mainView
}
@ -189,12 +190,16 @@ class UserGpxInfoFragment : BaseDialogFragment() {
}
}
private fun saveCurrentGpxToFile(listener: SavingTracksDbHelper.SaveGpxListener) {
app.savingTracksDbHelper.saveGpx(listener, app.getExternalFilesDir(null), gpxFile)
private fun saveCurrentGpxToFile(listener: OsmandLocationUtils.SaveGpxListener) {
if (!gpxFile.isEmpty) {
OsmandLocationUtils.saveGpx(app, listener, app.getExternalFilesDir(null)!!, gpxFile)
}
}
private fun readFromBundle(bundle: Bundle?) {
bundle?.also {
userId = it.getInt(USER_ID_KEY)
chatId = it.getLong(CHAT_ID_KEY)
startCalendar.timeInMillis = it.getLong(START_KEY)
endCalendar.timeInMillis = it.getLong(END_KEY)
}
@ -205,7 +210,10 @@ class UserGpxInfoFragment : BaseDialogFragment() {
}
private fun updateGpxInfo() {
gpxFile = app.savingTracksDbHelper.collectRecordedDataForUser(gpxFile.userId, gpxFile.chatId, startCalendar.timeInMillis, endCalendar.timeInMillis)
val emm = app.locationMessages.getMessagesForUserInChat(
userId, chatId, startCalendar.timeInMillis, endCalendar.timeInMillis)
gpxFile = OsmandLocationUtils.convertLocationMessagesToGpxFiles(emm).firstOrNull()?:GPXUtilities.GPXFile()
updateGPXStatisticRow()
updateDateAndTimeButtons()
updateGPXMap()
@ -229,7 +237,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private fun updateGPXMap() {
saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener {
OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) {
val mgr = activity?.getSystemService(Context.WINDOW_SERVICE)
if (mgr != null) {
@ -242,7 +250,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
}
}
override fun onSavingGpxError(warnings: MutableList<String>?) {
override fun onSavingGpxError(warnings: List<String>?) {
log.debug("onSavingGpxError ${warnings?.firstOrNull()}")
}
})
@ -299,18 +307,21 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private const val TAG = "UserGpxInfoFragment"
private const val START_KEY = "start_key"
private const val END_KEY = "end_key"
private const val USER_ID_KEY = "user_id_key"
private const val CHAT_ID_KEY = "chat_id_key"
private const val GPX_TRACK_COLOR = -65536
fun showInstance(fm: FragmentManager, gpxFile: GPXUtilities.GPXFile, start: Long, end: Long): Boolean {
fun showInstance(fm: FragmentManager,userId:Int,chatId:Long, start: Long, end: Long): Boolean {
return try {
val fragment = UserGpxInfoFragment().apply {
arguments = Bundle().apply {
putInt(USER_ID_KEY, userId)
putLong(CHAT_ID_KEY, chatId)
putLong(START_KEY, start)
putLong(END_KEY, end)
}
}
fragment.gpxFile = gpxFile
fragment.show(fm, TAG)
true
} catch (e: RuntimeException) {

View file

@ -0,0 +1,557 @@
package net.osmand.telegram.utils
import android.os.AsyncTask
import net.osmand.Location
import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.LocationMessages
import net.osmand.telegram.helpers.LocationMessages.BufferMessage
import net.osmand.telegram.helpers.LocationMessages.LocationMessage
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.util.GeoPointParserUtil
import net.osmand.util.MapUtils
import org.drinkless.td.libcore.telegram.TdApi
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
object OsmandLocationUtils {
const val DEVICE_PREFIX = "Device: "
const val LOCATION_PREFIX = "Location: "
const val LAST_LOCATION_PREFIX = "Last location: "
const val UPDATED_PREFIX = "Updated: "
const val USER_TEXT_LOCATION_TITLE = "\uD83D\uDDFA OsmAnd sharing:"
const val SHARING_LINK = "https://play.google.com/store/apps/details?id=net.osmand.telegram"
const val ALTITUDE_PREFIX = "Altitude: "
const val SPEED_PREFIX = "Speed: "
const val HDOP_PREFIX = "Horizontal precision: "
const val NOW = "now"
const val FEW_SECONDS_AGO = "few seconds ago"
const val SECONDS_AGO_SUFFIX = " seconds ago"
const val MINUTES_AGO_SUFFIX = " minutes ago"
const val HOURS_AGO_SUFFIX = " hours ago"
const val UTC_FORMAT_SUFFIX = " UTC"
val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
fun getLastUpdatedTime(message: TdApi.Message): Int {
val content = message.content
return when (content) {
is MessageOsmAndBotLocation -> content.lastUpdated
is MessageUserLocation -> content.lastUpdated
else -> Math.max(message.editDate, message.date)
}
}
fun getOsmAndBotDeviceName(message: TdApi.Message): String {
var deviceName = ""
if (message.replyMarkup is TdApi.ReplyMarkupInlineKeyboard) {
val replyMarkup = message.replyMarkup as TdApi.ReplyMarkupInlineKeyboard
try {
deviceName = replyMarkup.rows[0][1].text.split("\\s".toRegex())[1]
} catch (e: Exception) {
}
}
return deviceName
}
fun parseOsmAndBotLocation(message: TdApi.Message): MessageOsmAndBotLocation {
val messageLocation = message.content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = getOsmAndBotDeviceName(message)
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = getLastUpdatedTime(message)
}
}
fun parseUserMapLocation(message: TdApi.Message): MessageUserLocation {
val messageLocation = message.content as TdApi.MessageLocation
return MessageUserLocation().apply {
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = getLastUpdatedTime(message)
}
}
fun parseMessage(message: TdApi.Message, helper: TelegramHelper, previousMessage: LocationMessage?): LocationMessage? {
var locationMessage: LocationMessage? = null
val oldContent = message.content
val messageType = getMessageType(message,helper)
var parsedMessageContent: MessageLocation? = null
if (messageType != -1) {
parsedMessageContent = when (messageType) {
LocationMessages.TYPE_BOT_TEXT -> {
parseTextLocation((oldContent as TdApi.MessageText).text)
}
LocationMessages.TYPE_USER_TEXT -> {
if (oldContent is TdApi.MessageText) {
parseTextLocation(oldContent.text, false)
} else {
oldContent as MessageUserLocation
}
}
LocationMessages.TYPE_BOT_MAP -> {
parseOsmAndBotLocation(message)
}
LocationMessages.TYPE_USER_MAP -> {
parseUserMapLocation(message)
}
else -> null
}
}
if (parsedMessageContent != null) {
val distanceFromPrev = if (previousMessage != null) {
MapUtils.getDistance(previousMessage.lat, previousMessage.lon,
parsedMessageContent.lat, parsedMessageContent.lon)
} else 0.0
locationMessage = LocationMessage(helper.getSenderMessageId(message), message.chatId, parsedMessageContent.lat,
parsedMessageContent.lon, parsedMessageContent.altitude, parsedMessageContent.speed, parsedMessageContent.hdop,
parsedMessageContent.bearing, parsedMessageContent.lastUpdated * 1000L, messageType, message.id, distanceFromPrev)
}
return locationMessage
}
fun getMessageType(message: TdApi.Message, helper: TelegramHelper): Int {
val fromBot = helper.isOsmAndBot(message.senderUserId)
val viaBot = helper.isOsmAndBot(message.viaBotUserId)
val oldContent = message.content
return if (oldContent is TdApi.MessageText) {
if (fromBot || viaBot) {
LocationMessages.TYPE_BOT_TEXT
} else {
LocationMessages.TYPE_USER_TEXT
}
} else if (oldContent is TdApi.MessageLocation) {
if (fromBot || viaBot) {
LocationMessages.TYPE_BOT_MAP
} else {
LocationMessages.TYPE_USER_MAP
}
} else if (oldContent is MessageLocation) {
oldContent.type
} else {
-1
}
}
fun formatLocation(sig: Location): String {
return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude)
}
fun formatLocation(sig: LocationMessage): String {
return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon)
}
fun formatLocation(sig: BufferMessage): String {
return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon)
}
fun formatFullTime(ti: Long): String {
val dt = Date(ti)
return UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + " UTC"
}
fun parseOsmAndBotLocationContent(oldContent: MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation {
val messageLocation = content as TdApi.MessageLocation
return MessageOsmAndBotLocation().apply {
name = oldContent.name
lat = messageLocation.location.latitude
lon = messageLocation.location.longitude
lastUpdated = (System.currentTimeMillis() / 1000).toInt()
type = LocationMessages.TYPE_BOT_MAP
}
}
fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean = true): MessageLocation {
val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserLocation()
res.type = if (botLocation) LocationMessages.TYPE_BOT_TEXT else LocationMessages.TYPE_USER_TEXT
var locationNA = false
for (s in text.text.lines()) {
when {
s.startsWith(DEVICE_PREFIX) -> {
if (res is MessageOsmAndBotLocation) {
res.name = s.removePrefix(DEVICE_PREFIX)
}
}
s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> {
var locStr: String
var parse = true
if (s.startsWith(LAST_LOCATION_PREFIX)) {
locStr = s.removePrefix(LAST_LOCATION_PREFIX)
if (!locationNA) {
parse = false
}
} else {
locStr = s.removePrefix(LOCATION_PREFIX)
if (locStr.trim() == "n/a") {
locationNA = true
parse = false
}
}
if (parse) {
try {
val urlTextEntity =
text.entities.firstOrNull { it.type is TdApi.TextEntityTypeTextUrl }
if (urlTextEntity != null && urlTextEntity.offset == text.text.indexOf(
locStr
)
) {
val url = (urlTextEntity.type as TdApi.TextEntityTypeTextUrl).url
val point: GeoPointParserUtil.GeoParsedPoint? =
GeoPointParserUtil.parse(url)
if (point != null) {
res.lat = point.latitude
res.lon = point.longitude
}
} else {
val (latS, lonS) = locStr.split(" ")
res.lat = latS.dropLast(1).toDouble()
res.lon = lonS.toDouble()
val timeIndex = locStr.indexOf("(")
if (timeIndex != -1) {
val updatedS = locStr.substring(timeIndex, locStr.length)
res.lastUpdated =
(parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
s.startsWith(ALTITUDE_PREFIX) -> {
val altStr = s.removePrefix(ALTITUDE_PREFIX)
try {
val alt = altStr.split(" ").first()
res.altitude = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(SPEED_PREFIX) -> {
val altStr = s.removePrefix(SPEED_PREFIX)
try {
val alt = altStr.split(" ").first()
res.speed = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(HDOP_PREFIX) -> {
val altStr = s.removePrefix(HDOP_PREFIX)
try {
val alt = altStr.split(" ").first()
res.hdop = alt.toDouble()
} catch (e: Exception) {
e.printStackTrace()
}
}
s.startsWith(UPDATED_PREFIX) -> {
if (res.lastUpdated == 0) {
val updatedStr = s.removePrefix(UPDATED_PREFIX)
val endIndex = updatedStr.indexOf("(")
val updatedS = updatedStr.substring(
0,
if (endIndex != -1) endIndex else updatedStr.length
)
val parsedTime = (parseTime(updatedS.trim()) / 1000).toInt()
val currentTime = (System.currentTimeMillis() / 1000) - 1
res.lastUpdated =
if (parsedTime < currentTime) parsedTime else currentTime.toInt()
}
}
}
}
return res
}
fun parseTime(timeS: String): Long {
try {
when {
timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000
timeS.endsWith(SECONDS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX)
return System.currentTimeMillis() - locStr.toLong() * 1000
}
timeS.endsWith(MINUTES_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX)
val minutes = locStr.toLong()
return System.currentTimeMillis() - minutes * 60 * 1000
}
timeS.endsWith(HOURS_AGO_SUFFIX) -> {
val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX)
val hours = locStr.toLong()
return (System.currentTimeMillis() - hours * 60 * 60 * 1000)
}
timeS.endsWith(UTC_FORMAT_SUFFIX) -> {
val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX)
val (latS, lonS) = locStr.split(" ")
val date = UTC_DATE_FORMAT.parse(latS)
val time = UTC_TIME_FORMAT.parse(lonS)
val res = date.time + time.time
return res
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return 0
}
fun getTextMessageContent(updateId: Int, location: LocationMessage): TdApi.InputMessageText {
val entities = mutableListOf<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}")))
builder.append("$locationMessage\n")
if (location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hdop != 0.0 && location.speed == 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
}
fun getTextMessageContent(updateId: Int, location: BufferMessage): TdApi.InputMessageText {
val entities = mutableListOf<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}")))
builder.append("$locationMessage\n")
if (location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hdop != 0.0 && location.speed == 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
}
fun convertLocationMessagesToGpxFiles(items: List<LocationMessage>, newGpxPerChat: Boolean = true): List<GPXUtilities.GPXFile> {
val dataTracks = ArrayList<GPXUtilities.GPXFile>()
var previousTime: Long = -1
var previousChatId: Long = -1
var previousUserId = -1
var segment: GPXUtilities.TrkSegment? = null
var track: GPXUtilities.Track? = null
var gpx: GPXUtilities.GPXFile? = null
items.forEach {
val userId = it.userId
val chatId = it.chatId
val time = it.time
if (previousUserId != userId || (newGpxPerChat && previousChatId != chatId)) {
gpx = GPXUtilities.GPXFile()
gpx!!.chatId = chatId
gpx!!.userId = userId
previousTime = 0
track = null
segment = null
dataTracks.add(gpx!!)
}
val pt = GPXUtilities.WptPt()
pt.userId = userId
pt.chatId = chatId
pt.lat = it.lat
pt.lon = it.lon
pt.ele = it.altitude
pt.speed = it.speed
pt.hdop = it.hdop
pt.time = time
val currentInterval = Math.abs(time - previousTime)
if (track != null) {
if (currentInterval < 30 * 60 * 1000) {
// 30 minute - same segment
segment!!.points.add(pt)
} else {
segment = GPXUtilities.TrkSegment()
segment!!.points.add(pt)
track!!.segments.add(segment)
}
} else {
track = GPXUtilities.Track()
segment = GPXUtilities.TrkSegment()
track!!.segments.add(segment)
segment!!.points.add(pt)
gpx!!.tracks.add(track)
}
previousTime = time
previousUserId = userId
previousChatId = chatId
}
return dataTracks
}
fun saveGpx(app: TelegramApplication, listener: SaveGpxListener, dir: File, gpxFile: GPXUtilities.GPXFile) {
if (!gpxFile.isEmpty) {
val task = SaveGPXTrackToFileTask(app, listener, gpxFile, dir, 0)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
}
abstract class MessageLocation : TdApi.MessageContent() {
var lat: Double = Double.NaN
internal set
var lon: Double = Double.NaN
internal set
var lastUpdated: Int = 0
internal set
var speed: Double = 0.0
internal set
var altitude: Double = 0.0
internal set
var hdop: Double = 0.0
internal set
var bearing: Double = 0.0
internal set
var type: Int = -1
internal set
override fun getConstructor() = -1
abstract fun isValid(): Boolean
}
class MessageOsmAndBotLocation : MessageLocation() {
var name: String = ""
internal set
override fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN
}
class MessageUserLocation : MessageLocation() {
override fun isValid() = lat != Double.NaN && lon != Double.NaN
}
private class SaveGPXTrackToFileTask internal constructor(
private val app: TelegramApplication, private val listener: SaveGpxListener?,
private val gpxFile: GPXUtilities.GPXFile, private val dir: File, private val userId: Int
) :
AsyncTask<Void, Void, List<String>>() {
override fun doInBackground(vararg params: Void): List<String> {
val warnings = ArrayList<String>()
dir.mkdirs()
if (dir.parentFile.canWrite()) {
if (dir.exists()) {
// save file
var fout = File(dir, "$userId.gpx")
if (!gpxFile.isEmpty) {
val pt = gpxFile.findPointToShow()
val user = app.telegramHelper.getUser(pt!!.userId)
val fileName: String
fileName = if (user != null) {
(TelegramUiHelper.getUserName(user) + "_" + SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(Date(pt.time)))
} else {
userId.toString() + "_" + SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(Date(pt.time))
}
fout = File(dir, "$fileName.gpx")
var ind = 1
while (fout.exists()) {
fout = File(dir, "${fileName}_${++ind}.gpx")
}
}
val warn = GPXUtilities.writeGpxFile(fout, gpxFile, app)
if (warn != null) {
warnings.add(warn)
return warnings
}
}
}
return warnings
}
override fun onPostExecute(warnings: List<String>?) {
if (listener != null) {
if (warnings != null && warnings.isEmpty()) {
listener.onSavingGpxFinish(gpxFile.path)
} else {
listener.onSavingGpxError(warnings)
}
}
}
}
interface SaveGpxListener {
fun onSavingGpxFinish(path: String)
fun onSavingGpxError(warnings: List<String>?)
}
}