diff --git a/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml b/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml index 08d8a25c1d..0f70f54286 100644 --- a/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml +++ b/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml @@ -171,6 +171,63 @@ + + + + + + + + + + + + + + + Collected + Gps points + Sent Monitoring is enabled Monitoring is disabled time on the move diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt index 2addc53573..33758e4ad3 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt @@ -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 diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt index 4b90dd5a93..e6c2ebc7e4 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt @@ -13,10 +13,12 @@ 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.LocationMessages 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.* @@ -120,6 +122,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis override fun onDestroy() { super.onDestroy() val app = app() + app.locationMessages.saveMessages() app.telegramHelper.stopLiveMessagesUpdates() app.telegramHelper.removeIncomingMessagesListener(this) app.telegramHelper.removeOutgoingMessagesListener(this) @@ -274,6 +277,12 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) { app().showLocationHelper.startShowMessagesTask(chatId, *messages) + messages.forEach { + val locationMessage = OsmandLocationUtils.parseMessage(it, app().telegramHelper, LocationMessages.LocationMessage.STATUS_SENT) + if (locationMessage != null) { + app().locationMessages.addLocationMessage(locationMessage) + } + } } override fun onDeleteChatLocationMessages(chatId: Long, messages: List) { diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt index 8b9cb02a60..f25f7c93d5 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt @@ -851,6 +851,7 @@ class TelegramSettings(private val app: TelegramApplication) { var lastSuccessfulSendTimeMs = -1L var lastSendTextMessageTime = -1 var lastSendMapMessageTime = -1 + var bufferedMessages = 0 var pendingTextMessage = false var pendingMapMessage = false var shouldSendViaBotMessage = false diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/LocationMessages.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/LocationMessages.kt new file mode 100644 index 0000000000..bf49ff9725 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/LocationMessages.kt @@ -0,0 +1,220 @@ +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 org.apache.commons.logging.Log +import java.util.* + +class LocationMessages(val app: TelegramApplication) { + + private val log: Log = PlatformUtil.getLog(LocationMessages::class.java) + + private val locationMessages = ArrayList() + + private val sqliteHelper: SQLiteHelper + + init { + sqliteHelper = SQLiteHelper(app) + readMessages() + } + + fun getLocationMessages(): List { + return this.locationMessages + } + + fun getPreparedToShareMessages(): List { + val currentUserId = app.telegramHelper.getCurrentUserId() + return locationMessages.filter { it.userId == currentUserId && it.status == LocationMessage.STATUS_PREPARING }.sortedBy { it.date } + } + + fun getOutgoingMessagesToChat(chatId: Long): List { + val currentUserId = app.telegramHelper.getCurrentUserId() + return locationMessages.filter { it.userId == currentUserId && it.chatId == chatId }.sortedBy { it.date } + } + + fun getOutgoingMessagesToChatFromDate(chatId: Long, date:Long): List { + val currentUserId = app.telegramHelper.getCurrentUserId() + return locationMessages.filter { it.userId == currentUserId && it.chatId == chatId && it.date > date }.sortedBy { it.date } + } + + fun getSentOutgoingMessagesToChat(chatId: Long): List { + val currentUserId = app.telegramHelper.getCurrentUserId() + return locationMessages.filter { it.userId == currentUserId && it.chatId == chatId && it.status == LocationMessages.LocationMessage.STATUS_SENT }.sortedBy { it.date } + } + + fun getIncomingMessages(): List { + val currentUserId = app.telegramHelper.getCurrentUserId() + return locationMessages.filter { it.userId != currentUserId }.sortedBy { it.date } + } + + fun addLocationMessage(locationMessage: LocationMessage) { + log.debug("addLocationMessage $locationMessage") + synchronized(locationMessages) { + locationMessages.add(locationMessage) + } + } + + fun removeMessage(locationMessage: LocationMessage) { + synchronized(locationMessages) { + locationMessages.remove(locationMessage) + } + } + + fun saveMessages() { + clearMessages() + synchronized(locationMessages) { + sqliteHelper.addLocationMessages(locationMessages) + } + } + + fun clearMessages() { + sqliteHelper.clearLocationMessages() + } + + fun collectRecordedDataForUser(userId: Int, chatId: Long, start: Long, end: Long): List { + return if (chatId == 0L) { + locationMessages.sortedWith(compareBy({ it.userId }, { it.chatId })).filter { it.userId == userId&&it.date in (start + 1)..(end - 1) } + } else { + locationMessages.sortedWith(compareBy({ it.userId }, { it.chatId })).filter { + it.chatId == chatId && it.userId == userId && it.status == LocationMessage.STATUS_SENT && it.date in (start + 1)..(end - 1) } + } + } + + fun collectRecordedDataForUsers(start: Long, end: Long, ignoredUsersIds: ArrayList): List { + return locationMessages.sortedWith(compareBy({ it.userId }, { it.chatId })).filter { + it.date in (start + 1)..(end - 1) && !ignoredUsersIds.contains(it.userId) + } + } + + private fun readMessages() { + val messages = sqliteHelper.getLocationMessages() + synchronized(locationMessages) { + locationMessages.addAll(messages) + } + } + + private class SQLiteHelper(context: Context) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + + override fun onCreate(db: SQLiteDatabase) { + db.execSQL(TRACKS_TABLE_CREATE) + db.execSQL("CREATE INDEX $TRACK_DATE_INDEX ON $TRACK_TABLE_NAME (\"$TRACK_COL_DATE\" DESC);") + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL(TRACKS_TABLE_DELETE) + onCreate(db) + } + + internal fun addLocationMessages(locationMessages: List) { + locationMessages.forEach { + writableDatabase?.execSQL(TRACKS_TABLE_INSERT, + arrayOf(it.userId, it.chatId, it.lat, it.lon, it.altitude, it.speed, it.hdop, it.bearing, it.date, it.type, it.status, it.messageId)) + } + } + + internal fun getLocationMessages(): Set { + val res = HashSet() + readableDatabase?.rawQuery(TRACKS_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 date = cursor.getLong(7) + val bearing = cursor.getDouble(8) + val textInfo = cursor.getInt(9) + val status = cursor.getInt(10) + val messageId = cursor.getLong(11) + + return LocationMessage(userId, chatId, lat, lon, altitude, speed, hdop, bearing, date, textInfo, status, messageId) + } + + internal fun clearLocationMessages() { + writableDatabase?.execSQL(TRACKS_TABLE_CLEAR) + } + + companion object { + + private const val DATABASE_NAME = "location_messages" + private const val DATABASE_VERSION = 2 + + private const val TRACK_TABLE_NAME = "track" + private const val TRACK_COL_USER_ID = "user_id" + private const val TRACK_COL_CHAT_ID = "chat_id" + private const val TRACK_COL_DATE = "date" + private const val TRACK_COL_LAT = "lat" + private const val TRACK_COL_LON = "lon" + private const val TRACK_COL_ALTITUDE = "altitude" + private const val TRACK_COL_SPEED = "speed" + private const val TRACK_COL_HDOP = "hdop" + private const val TRACK_COL_BEARING = "bearing" + private const val TRACK_COL_TYPE = + "type" // 0 = user map message, 1 = user text message, 2 = bot map message, 3 = bot text message + private const val TRACK_COL_MESSAGE_STATUS = + "status" // 0 = preparing , 1 = pending, 2 = sent, 3 = error + private const val TRACK_COL_MESSAGE_ID = "message_id" + + private const val TRACK_DATE_INDEX = "date_index" + + private const val TRACKS_TABLE_INSERT = + ("INSERT INTO $TRACK_TABLE_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_BEARING, $TRACK_COL_DATE, $TRACK_COL_TYPE, $TRACK_COL_MESSAGE_STATUS, $TRACK_COL_MESSAGE_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + + private const val TRACKS_TABLE_CREATE = + ("CREATE TABLE IF NOT EXISTS $TRACK_TABLE_NAME ($TRACK_COL_USER_ID long, $TRACK_COL_CHAT_ID long,$TRACK_COL_LAT double, $TRACK_COL_LON double, $TRACK_COL_ALTITUDE double, $TRACK_COL_SPEED float, $TRACK_COL_HDOP double, $TRACK_COL_BEARING double, $TRACK_COL_DATE long, $TRACK_COL_TYPE int, $TRACK_COL_MESSAGE_STATUS int, $TRACK_COL_MESSAGE_ID long )") + + private const val TRACKS_TABLE_SELECT = + "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_BEARING, $TRACK_COL_DATE, $TRACK_COL_TYPE, $TRACK_COL_MESSAGE_STATUS, $TRACK_COL_MESSAGE_ID FROM $TRACK_TABLE_NAME" + + private const val TRACKS_TABLE_CLEAR = "DELETE FROM $TRACK_TABLE_NAME" + + private const val TRACKS_TABLE_DELETE = "DROP TABLE IF EXISTS $TRACK_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 date: Long, + val type: Int, + var status: Int, + var messageId: Long + ) { + + companion object { + + const val STATUS_PREPARING = 0 + const val STATUS_PENDING = 1 + const val STATUS_SENT = 2 + const val STATUS_ERROR = 3 + + const val TYPE_USER_MAP = 0 + const val TYPE_USER_TEXT = 1 + const val TYPE_BOT_MAP = 2 + const val TYPE_BOT_TEXT = 3 + } + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/MessagesDbHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/MessagesDbHelper.kt deleted file mode 100644 index dcd4d67c46..0000000000 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/MessagesDbHelper.kt +++ /dev/null @@ -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() - - 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) { - 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) { - messages.forEach { - writableDatabase?.execSQL(MESSAGES_TABLE_INSERT, arrayOf(it.chatId, it.messageId)) - } - } - - internal fun getMessages(): Set { - val res = HashSet() - 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) -} diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/SavingTracksDbHelper.java b/OsmAnd-telegram/src/net/osmand/telegram/helpers/SavingTracksDbHelper.java deleted file mode 100644 index feac28fe39..0000000000 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/SavingTracksDbHelper.java +++ /dev/null @@ -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 messages) { - - } - - @Override - public void updateLocationMessages() { - - } - }); - app.getTelegramHelper().addOutgoingMessagesListener(new TelegramHelper.TelegramOutgoingMessagesListener() { - - @Override - public void onUpdateMessages(@NotNull List messages) { - for (TdApi.Message message : messages) { - updateLocationMessage(message); - } - } - - @Override - public void onDeleteMessages(long chatId, @NotNull List 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 collectRecordedDataForUsers(long start, long end, ArrayList ignoredUsersIds) { - ArrayList 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 dataTracks, long start, long end, ArrayList 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> { - - 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 doInBackground(Void... params) { - List warnings = new ArrayList(); - 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 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 warnings); - } -} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt index e8040846ff..f7691710f6 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt @@ -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.LocationMessage import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.utils.AndroidNetworkUtils import net.osmand.telegram.utils.BASE_URL @@ -48,36 +49,9 @@ 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()) { + addNewLocationMessages(location, app.telegramHelper.getCurrentUserId()) + shareMessages() } lastLocationMessageSentTime = System.currentTimeMillis() } @@ -158,25 +132,119 @@ 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 addNewLocationMessages(location: Location, userId: Int) { + val chatsShareInfo = app.settings.getChatsShareInfo() + val latitude = location.latitude + val longitude = location.longitude + val isBot = app.settings.currentSharingMode != userId.toString() + val types = mutableListOf() + + when (app.settings.shareTypeValue) { + SHARE_TYPE_MAP -> { + types.add(if (isBot) LocationMessage.TYPE_BOT_MAP else LocationMessage.TYPE_USER_MAP) + } + SHARE_TYPE_TEXT -> { + types.add(if (isBot) LocationMessage.TYPE_BOT_TEXT else LocationMessage.TYPE_USER_TEXT) + } + SHARE_TYPE_MAP_AND_TEXT -> { + types.add(if (isBot) LocationMessage.TYPE_BOT_MAP else LocationMessage.TYPE_USER_MAP) + types.add(if (isBot) LocationMessage.TYPE_BOT_TEXT else LocationMessage.TYPE_USER_TEXT) + } + } + chatsShareInfo.values.forEach { shareInfo -> + types.forEach { + val message = LocationMessage(userId, shareInfo.chatId, latitude, longitude, location.altitude, location.speed.toDouble(), + location.accuracy.toDouble(), location.bearing.toDouble(), location.time, it, LocationMessage.STATUS_PREPARING, shareInfo.currentMapMessageId) + app.locationMessages.addLocationMessage(message) + } + } + } + + private fun shareMessages() { + var bufferedMessagesFull = false + app.locationMessages.getPreparedToShareMessages().forEach { + val shareChatInfo = app.settings.getChatsShareInfo()[it.chatId] + if (shareChatInfo != null) { + bufferedMessagesFull = shareChatInfo.bufferedMessages < 10 + if (bufferedMessagesFull) { + when { + it.type == LocationMessage.TYPE_USER_TEXT -> { + shareChatInfo.bufferedMessages++ + it.status = LocationMessage.STATUS_PENDING + app.telegramHelper.sendLiveLocationText(shareChatInfo, it) + } + it.type == LocationMessage.TYPE_USER_MAP -> { + shareChatInfo.bufferedMessages++ + it.status = LocationMessage.STATUS_PENDING + app.telegramHelper.sendLiveLocationMap(shareChatInfo, it) + } + it.type == LocationMessage.TYPE_BOT_TEXT -> { + sendLocationToBot(it, app.settings.currentSharingMode, shareChatInfo, SHARE_TYPE_TEXT) + } + it.type == LocationMessage.TYPE_BOT_MAP -> { + sendLocationToBot(it, app.settings.currentSharingMode, shareChatInfo, SHARE_TYPE_MAP) + } + } + } + } + } + if (bufferedMessagesFull) { + checkNetworkType() + } + } + + 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: LocationMessage, sharingMode: String, shareInfo: TelegramSettings.ShareChatInfo, shareType: String) { + if (app.isInternetConnectionAvailable) { + locationMessage.status = LocationMessage.STATUS_PENDING + 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() + + locationMessage.status = if (success) LocationMessage.STATUS_SENT else LocationMessage.STATUS_ERROR + 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: LocationMessage, 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) { + private fun checkResultAndUpdateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map):Boolean { if (result != null) { try { val jsonResult = JSONObject(result) @@ -186,22 +254,12 @@ class ShareLocationHelper(private val app: TelegramApplication) { chatsShareInfo.forEach { (_, shareInfo) -> shareInfo.lastSuccessfulSendTimeMs = currentTime } + return true } } catch (e: JSONException) { } } - } - - private fun checkAndSendViaBotMessages(chatsShareInfo: Map, 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() { @@ -216,4 +274,4 @@ class ShareLocationHelper(private val app: TelegramApplication) { const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC - 1 const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC + 1 } -} +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt index 9d52abdb37..ee3d12d90b 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt @@ -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.MessageUserTextLocation +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,7 +79,7 @@ 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()))) { var userName = "" diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt index be9c2886a4..81f67c8421 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt @@ -3,15 +3,20 @@ 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.MessageUserTextLocation +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 @@ -36,32 +41,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 +151,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 +197,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 @@ -349,6 +321,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 { @@ -564,7 +542,7 @@ class TelegramHelper private constructor() { resultArticles.forEach { client?.send(TdApi.SendInlineQueryResultMessage(shareInfo.chatId, 0, true, true, inlineQueryResults.inlineQueryId, it.id)) { obj -> - handleTextLocationMessageUpdate(obj, shareInfo) + handleTextLocationMessageUpdate(obj, shareInfo, null) } } } @@ -728,7 +706,7 @@ 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") + log.debug("addNewMessage: ${message.id}") val fromBot = isOsmAndBot(message.senderUserId) val viaBot = isOsmAndBot(message.viaBotUserId) val oldContent = message.content @@ -753,7 +731,7 @@ 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) } } @@ -779,7 +757,7 @@ 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() } } @@ -811,7 +789,7 @@ class TelegramHelper private constructor() { if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) { client?.send( TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, null)) { obj -> - handleMapLocationMessageUpdate(obj, shareInfo) + handleMapLocationMessageUpdate(obj, shareInfo, null) } } needRefreshActiveLiveLocationMessages = true @@ -856,7 +834,7 @@ class TelegramHelper private constructor() { private fun recreateLiveLocationMessage( shareInfo: TelegramSettings.ShareChatInfo, - content: TdApi.InputMessageContent + content: TdApi.InputMessageContent,locationMessage: LocationMessages.LocationMessage? ) { if (shareInfo.chatId != -1L) { val array = LongArray(1) @@ -869,7 +847,7 @@ class TelegramHelper private constructor() { log.debug("recreateLiveLocationMessage - ${array[0]}") client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj -> when (obj.constructor) { - TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content) + TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content,locationMessage) TdApi.Error.CONSTRUCTOR -> { val error = obj as TdApi.Error if (error.code != IGNORED_ERROR_CODE) { @@ -886,12 +864,12 @@ class TelegramHelper private constructor() { needRefreshActiveLiveLocationMessages = true } - private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) { + private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent, locationMessage: LocationMessages.LocationMessage?) { needRefreshActiveLiveLocationMessages = true log.debug("sendNewLiveLocationMessage") client?.send( TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj -> - handleMapLocationMessageUpdate(obj, shareInfo) + handleMapLocationMessageUpdate(obj, shareInfo, locationMessage) } } @@ -913,52 +891,79 @@ class TelegramHelper private constructor() { log.debug("sendLiveLocationImpl - $msgId pendingMapMessage ${shareInfo.pendingMapMessage}") if (msgId != -1L) { if (shareInfo.shouldDeletePreviousMapMessage) { - recreateLiveLocationMessage(shareInfo, content) + recreateLiveLocationMessage(shareInfo, content, null) shareInfo.shouldDeletePreviousMapMessage = false shareInfo.currentMapMessageId = -1 } else { log.debug("EditMessageLiveLocation - $msgId") client?.send( TdApi.EditMessageLiveLocation(chatId, msgId, null, location)) { obj -> - handleMapLocationMessageUpdate(obj, shareInfo) + handleMapLocationMessageUpdate(obj, shareInfo, null) } } } else if (!shareInfo.pendingMapMessage || shareInfo.pendingMapMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) { - sendNewLiveLocationMessage(shareInfo, content) + sendNewLiveLocationMessage(shareInfo, content, null) } } } - fun sendLiveLocationText(chatsShareInfo: Map, location: Location) { - chatsShareInfo.forEach { (chatId, shareInfo) -> - if (shareInfo.getChatLiveMessageExpireTime() <= 0) { - return@forEach + fun sendLiveLocationMap(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.LocationMessage) { + 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 + } else { + shareInfo.livePeriod.toInt() } - val msgId = shareInfo.currentTextMessageId - if (msgId == -1L) { - shareInfo.updateTextMessageId = 1 + 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, locationMessage) + shareInfo.shouldDeletePreviousMapMessage = false + shareInfo.currentMapMessageId = -1 + } else { + log.debug("EditMessageLiveLocation - $msgId") + client?.send( + TdApi.EditMessageLiveLocation(shareInfo.chatId, msgId, null, location)) { obj -> + handleMapLocationMessageUpdate(obj, shareInfo, locationMessage) + } } - 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.pendingMapMessage || shareInfo.pendingMapMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) { + sendNewLiveLocationMessage(shareInfo, content, locationMessage) + } + } + + fun sendLiveLocationText(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.LocationMessage) { + val msgId = shareInfo.currentTextMessageId + if (msgId == -1L) { + shareInfo.updateTextMessageId = 1 + } + val content = OsmandLocationUtils.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, location) + shareInfo.shouldDeletePreviousTextMessage = false + } else { + client?.send(TdApi.EditMessageText(shareInfo.chatId, msgId, null, content)) { obj -> + handleTextLocationMessageUpdate(obj, shareInfo, location) } - } 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) + } + } else { + if (!shareInfo.pendingTextMessage) { + client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, false, null, content)) { obj -> + handleTextLocationMessageUpdate(obj, shareInfo, location) } + } else if(timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC){ + } } } - private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) { + private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.LocationMessage?) { when (obj.constructor) { TdApi.Error.CONSTRUCTOR -> { val error = obj as TdApi.Error @@ -978,6 +983,7 @@ class TelegramHelper private constructor() { obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { shareInfo.hasSharingError = true needRefreshActiveLiveLocationMessages = true + location?.status = LocationMessages.LocationMessage.STATUS_ERROR outgoingMessagesListeners.forEach { it.onSendLiveLocationError(-1, "Map location message ${obj.id} failed to send") } @@ -985,10 +991,14 @@ class TelegramHelper private constructor() { obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> { shareInfo.pendingMapMessage = true shareInfo.lastSendMapMessageTime = obj.date + location?.status = LocationMessages.LocationMessage.STATUS_PENDING log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending") } else -> { shareInfo.hasSharingError = false + shareInfo.bufferedMessages-- + location?.messageId = obj.id + location?.status = LocationMessages.LocationMessage.STATUS_SENT outgoingMessagesListeners.forEach { it.onUpdateMessages(listOf(obj)) } @@ -999,7 +1009,7 @@ class TelegramHelper private constructor() { } } - private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) { + private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.LocationMessage?) { when (obj.constructor) { TdApi.Error.CONSTRUCTOR -> { val error = obj as TdApi.Error @@ -1017,7 +1027,9 @@ class TelegramHelper private constructor() { when { obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { shareInfo.hasSharingError = true + shareInfo.bufferedMessages-- needRefreshActiveLiveLocationMessages = true + location?.status = LocationMessages.LocationMessage.STATUS_ERROR outgoingMessagesListeners.forEach { it.onSendLiveLocationError(-1, "Text location message ${obj.id} failed to send") } @@ -1025,10 +1037,14 @@ class TelegramHelper private constructor() { obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> { shareInfo.pendingTextMessage = true shareInfo.lastSendTextMessageTime = obj.date + location?.status = LocationMessages.LocationMessage.STATUS_PENDING log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending") } else -> { shareInfo.hasSharingError = false + shareInfo.bufferedMessages-- + location?.messageId = obj.id + location?.status = LocationMessages.LocationMessage.STATUS_SENT outgoingMessagesListeners.forEach { it.onUpdateMessages(listOf(obj)) } @@ -1039,54 +1055,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() - 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 +1183,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 { override fun compareTo(other: OrderedChat): Int { diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt index 2a4f1b1436..33153b5fa0 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt @@ -6,8 +6,9 @@ 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.OsmandLocationUtils +import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserTextLocation +import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation import net.osmand.telegram.utils.GPXUtilities import org.drinkless.td.libcore.telegram.TdApi @@ -65,7 +66,7 @@ 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) @@ -172,7 +173,7 @@ object TelegramUiHelper { grayscalePhotoPath = helper.getUserGreyPhotoPath(user) placeholderId = R.drawable.img_user_picture userId = message.senderUserId - lastUpdated = helper.getLastUpdatedTime(message) + lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message) } } @@ -225,7 +226,7 @@ object TelegramUiHelper { userId = message.senderUserId privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat) chatWithBot = helper.isBot(userId) - lastUpdated = helper.getLastUpdatedTime(message) + lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message) } } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt index 073c9633f2..3d99af0a8f 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt @@ -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 diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt index 62d50ee90d..6e62c2576b 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt @@ -19,6 +19,7 @@ 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.OsmandAidlHelper import net.osmand.telegram.helpers.TelegramHelper import net.osmand.telegram.helpers.TelegramHelper.* @@ -183,7 +184,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene override fun onStop() { super.onStop() settings.save() - app.messagesDbHelper.saveMessages() + app.locationMessages.saveMessages() } override fun onDestroy() { @@ -291,6 +292,14 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene if (!app.showLocationHelper.showingLocation && settings.hasAnyChatToShowOnMap()) { app.showLocationHelper.startShowingLocation() } + if (app.telegramService == null) { + messages.forEach { + val locationMessage = OsmandLocationUtils.parseMessage(it, telegramHelper, LocationMessages.LocationMessage.STATUS_SENT) + if (locationMessage != null) { + app.locationMessages.addLocationMessage(locationMessage) + } + } + } } override fun onDeleteChatLocationMessages(chatId: Long, messages: List) {} @@ -341,7 +350,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.clearMessages() settings.clear() telegramHelper.logout() } else { diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt index 149f6c7cc1..a10603c9b8 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt @@ -714,6 +714,18 @@ class MyLocationTabFragment : Fragment(), TelegramListener { OsmandFormatter.getFormattedDuration(context!!, expiresIn, true) )})" } + holder.gpsPointsCollected?.apply { + if (shareInfo != null) { + val all = app.locationMessages.getOutgoingMessagesToChat(shareInfo.chatId) + text = "${all.size}" + } + } + holder.gpsPointsSent?.apply { + if (shareInfo != null) { + val sent = app.locationMessages.getOutgoingMessagesToChatFromDate(shareInfo.chatId, shareInfo.start * 1000) + text = "${sent.size}" + } + } } } @@ -751,10 +763,12 @@ 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) } } interface ActionButtonsListener { fun switchButtonsVisibility(visible: Boolean) } -} +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt index 48616165db..c3007c9217 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt @@ -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) diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/TimelineTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/TimelineTabFragment.kt index 608e9e5fde..708b430ce6 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/TimelineTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/TimelineTabFragment.kt @@ -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 @@ -21,8 +22,8 @@ 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 net.osmand.telegram.utils.OsmandLocationUtils import java.util.* @@ -45,6 +46,8 @@ class TimelineTabFragment : Fragment() { private var start = 0L private var end = 0L + private var updateEnable: Boolean = false + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -93,6 +96,17 @@ class TimelineTabFragment : Fragment() { return mainView } + override fun onResume() { + super.onResume() + updateEnable = true + startHandler() + } + + override fun onPause() { + super.onPause() + updateEnable = false + } + private fun setupBtnTextColor(textView: TextView) { textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light)) } @@ -162,32 +176,37 @@ 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() - val s = System.currentTimeMillis() - log.debug("updateList $s") val ignoredUsersIds = ArrayList() 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) + val locationMessages = app.locationMessages.collectRecordedDataForUser(currentUserId, 0, start, end) + OsmandLocationUtils.convertLocationMessagesToGpxFiles(locationMessages, false).forEach { + TelegramUiHelper.gpxToChatItem(telegramHelper, it, true)?.also { chatItem -> + res.add(chatItem) } } ignoredUsersIds.add(currentUserId) } - val gpxFiles = app.savingTracksDbHelper.collectRecordedDataForUsers(start, end, ignoredUsersIds) - val e = System.currentTimeMillis() - - gpxFiles.forEach { + val locationMessages = app.locationMessages.collectRecordedDataForUsers(start, end, ignoredUsersIds) + OsmandLocationUtils.convertLocationMessagesToGpxFiles(locationMessages).forEach { TelegramUiHelper.gpxToChatItem(telegramHelper, it,false)?.also { chatItem -> res.add(chatItem) } } adapter.items = sortAdapterItems(res) - log.debug("updateList $s dif: ${e - s}") } private fun sortAdapterItems(list: MutableList): MutableList { @@ -269,4 +288,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 + } } \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/UserGpxInfoFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/UserGpxInfoFragment.kt index cbfa2e8f46..a01e4803c5 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/UserGpxInfoFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/UserGpxInfoFragment.kt @@ -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 @@ -63,7 +63,6 @@ class UserGpxInfoFragment : BaseDialogFragment() { readFromBundle(savedInstanceState ?: arguments) val userId = gpxFile.userId - val chatId = gpxFile.chatId val user = app.telegramHelper.getUser(userId) if (user != null) { @@ -134,12 +133,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?) { + override fun onSavingGpxError(warnings: List?) { Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() } }) @@ -156,12 +156,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?) { + override fun onSavingGpxError(warnings: List?) { Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() } }) @@ -189,8 +189,10 @@ 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?) { @@ -205,7 +207,14 @@ class UserGpxInfoFragment : BaseDialogFragment() { } private fun updateGpxInfo() { - gpxFile = app.savingTracksDbHelper.collectRecordedDataForUser(gpxFile.userId, gpxFile.chatId, startCalendar.timeInMillis, endCalendar.timeInMillis) + gpxFile = OsmandLocationUtils.convertLocationMessagesToGpxFiles( + app.locationMessages.collectRecordedDataForUser( + gpxFile.userId, + gpxFile.chatId, + startCalendar.timeInMillis, + endCalendar.timeInMillis + ) + ).first() updateGPXStatisticRow() updateDateAndTimeButtons() updateGPXMap() @@ -229,7 +238,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 +251,7 @@ class UserGpxInfoFragment : BaseDialogFragment() { } } - override fun onSavingGpxError(warnings: MutableList?) { + override fun onSavingGpxError(warnings: List?) { log.debug("onSavingGpxError ${warnings?.firstOrNull()}") } }) diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt new file mode 100644 index 0000000000..ef1cf80f00 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt @@ -0,0 +1,474 @@ +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.TelegramHelper +import net.osmand.telegram.helpers.TelegramUiHelper +import net.osmand.util.GeoPointParserUtil +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 MessageUserTextLocation -> 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 parseMessage(message: TdApi.Message, helper: TelegramHelper, status: Int): LocationMessages.LocationMessage? { + var locationMessage: LocationMessages.LocationMessage? = null + val oldContent = message.content + + val fromBot = helper.isOsmAndBot(message.senderUserId) + val viaBot = helper.isOsmAndBot(message.viaBotUserId) + + var messageType: Int = -1 + val parsedMessageContent = + if (oldContent is TdApi.MessageText) { + when { + oldContent.text.text.startsWith(DEVICE_PREFIX) -> { + messageType = 3 + parseTextLocation(oldContent.text) + } + oldContent.text.text.startsWith(USER_TEXT_LOCATION_TITLE) -> { + messageType = 1 + parseTextLocation(oldContent.text, false) + } + else -> null + } + } else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) { + messageType = 2 + parseOsmAndBotLocation(message) + } else if (oldContent is MessageLocation) { + messageType = 0 + oldContent + } else { + null + } + + if (parsedMessageContent != null) { + locationMessage = LocationMessages.LocationMessage(message.senderUserId, message.chatId, parsedMessageContent.lat, + parsedMessageContent.lon, parsedMessageContent.altitude, parsedMessageContent.speed, parsedMessageContent.hdop, + parsedMessageContent.bearing, parsedMessageContent.lastUpdated * 1000L, messageType, status, message.id) + } + return locationMessage + } + + fun formatLocation(sig: Location): String { + return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude) + } + + fun formatLocation(sig: LocationMessages.LocationMessage): 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() + } + } + + 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 + } + + 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: LocationMessages.LocationMessage): TdApi.InputMessageText { + val entities = mutableListOf() + 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.date))) + } else { + builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.date), updateId)) + } + val textMessage = builder.toString().trim() + + return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true) + } + + fun convertLocationMessagesToGpxFiles(items: List, newGpxPerChat: Boolean = true): List { + val dataTracks = ArrayList() + + 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.date + 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 + + 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 + + } + + 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>() { + + override fun doInBackground(vararg params: Void): List { + val warnings = ArrayList() + 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?) { + 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?) + } +} \ No newline at end of file