Telegram database refactoring

This commit is contained in:
Chumva 2019-01-25 18:04:26 +02:00
parent bdeb1b6de9
commit 7b41a03c47
19 changed files with 1068 additions and 996 deletions

View file

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

View file

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

View file

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

View file

@ -13,10 +13,12 @@ import android.os.*
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import net.osmand.PlatformUtil 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.TelegramIncomingMessagesListener
import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener
import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandLocationUtils
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
import java.util.* import java.util.*
@ -120,6 +122,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
val app = app() val app = app()
app.locationMessages.saveMessages()
app.telegramHelper.stopLiveMessagesUpdates() app.telegramHelper.stopLiveMessagesUpdates()
app.telegramHelper.removeIncomingMessagesListener(this) app.telegramHelper.removeIncomingMessagesListener(this)
app.telegramHelper.removeOutgoingMessagesListener(this) app.telegramHelper.removeOutgoingMessagesListener(this)
@ -274,6 +277,12 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis
override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) { override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) {
app().showLocationHelper.startShowMessagesTask(chatId, *messages) 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<TdApi.Message>) { override fun onDeleteChatLocationMessages(chatId: Long, messages: List<TdApi.Message>) {

View file

@ -851,6 +851,7 @@ class TelegramSettings(private val app: TelegramApplication) {
var lastSuccessfulSendTimeMs = -1L var lastSuccessfulSendTimeMs = -1L
var lastSendTextMessageTime = -1 var lastSendTextMessageTime = -1
var lastSendMapMessageTime = -1 var lastSendMapMessageTime = -1
var bufferedMessages = 0
var pendingTextMessage = false var pendingTextMessage = false
var pendingMapMessage = false var pendingMapMessage = false
var shouldSendViaBotMessage = false var shouldSendViaBotMessage = false

View file

@ -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<LocationMessage>()
private val sqliteHelper: SQLiteHelper
init {
sqliteHelper = SQLiteHelper(app)
readMessages()
}
fun getLocationMessages(): List<LocationMessage> {
return this.locationMessages
}
fun getPreparedToShareMessages(): List<LocationMessage> {
val currentUserId = app.telegramHelper.getCurrentUserId()
return locationMessages.filter { it.userId == currentUserId && it.status == LocationMessage.STATUS_PREPARING }.sortedBy { it.date }
}
fun getOutgoingMessagesToChat(chatId: Long): List<LocationMessage> {
val currentUserId = app.telegramHelper.getCurrentUserId()
return locationMessages.filter { it.userId == currentUserId && it.chatId == chatId }.sortedBy { it.date }
}
fun getOutgoingMessagesToChatFromDate(chatId: Long, date:Long): List<LocationMessage> {
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<LocationMessage> {
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<LocationMessage> {
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<LocationMessages.LocationMessage> {
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<Int>): List<LocationMessages.LocationMessage> {
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<LocationMessage>) {
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<LocationMessage> {
val res = HashSet<LocationMessage>()
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
}
}
}

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package net.osmand.telegram.helpers
import net.osmand.Location import net.osmand.Location
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.* import net.osmand.telegram.*
import net.osmand.telegram.helpers.LocationMessages.LocationMessage
import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.notifications.TelegramNotification.NotificationType
import net.osmand.telegram.utils.AndroidNetworkUtils import net.osmand.telegram.utils.AndroidNetworkUtils
import net.osmand.telegram.utils.BASE_URL import net.osmand.telegram.utils.BASE_URL
@ -48,36 +49,9 @@ class ShareLocationHelper(private val app: TelegramApplication) {
lastLocation = location lastLocation = location
if (location != null) { if (location != null) {
val chatsShareInfo = app.settings.getChatsShareInfo() if (app.settings.getChatsShareInfo().isNotEmpty()) {
if (chatsShareInfo.isNotEmpty()) { addNewLocationMessages(location, app.telegramHelper.getCurrentUserId())
val latitude = location.latitude shareMessages()
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)
}
}
})
}
} }
lastLocationMessageSentTime = System.currentTimeMillis() lastLocationMessageSentTime = System.currentTimeMillis()
} }
@ -158,25 +132,119 @@ class ShareLocationHelper(private val app: TelegramApplication) {
refreshNotification() refreshNotification()
} }
private fun getDeviceSharingUrl(loc: Location, sharingMode: String): String { private fun addNewLocationMessages(location: Location, userId: Int) {
val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.latitude}&lon=${loc.longitude}" val chatsShareInfo = app.settings.getChatsShareInfo()
val latitude = location.latitude
val longitude = location.longitude
val isBot = app.settings.currentSharingMode != userId.toString()
val types = mutableListOf<Int>()
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) val builder = StringBuilder(url)
if (loc.hasBearing() && loc.bearing != 0.0f) { if (loc.bearing != 0.0) {
builder.append("&azi=${loc.bearing}") builder.append("&azi=${loc.bearing}")
} }
if (loc.hasSpeed() && loc.speed != 0.0f) { if (loc.speed != 0.0) {
builder.append("&spd=${loc.speed}") builder.append("&spd=${loc.speed}")
} }
if (loc.hasAltitude() && loc.altitude != 0.0) { if (loc.altitude != 0.0) {
builder.append("&alt=${loc.altitude}") builder.append("&alt=${loc.altitude}")
} }
if (loc.hasAccuracy() && loc.accuracy != 0.0f) { if (loc.hdop != 0.0) {
builder.append("&hdop=${loc.accuracy}") builder.append("&hdop=${loc.hdop}")
} }
return builder.toString() return builder.toString()
} }
private fun updateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>) { private fun checkResultAndUpdateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>):Boolean {
if (result != null) { if (result != null) {
try { try {
val jsonResult = JSONObject(result) val jsonResult = JSONObject(result)
@ -186,22 +254,12 @@ class ShareLocationHelper(private val app: TelegramApplication) {
chatsShareInfo.forEach { (_, shareInfo) -> chatsShareInfo.forEach { (_, shareInfo) ->
shareInfo.lastSuccessfulSendTimeMs = currentTime shareInfo.lastSuccessfulSendTimeMs = currentTime
} }
return true
} }
} catch (e: JSONException) { } catch (e: JSONException) {
} }
} }
} return false
private fun checkAndSendViaBotMessages(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: TdApi.Location, osmandBot: TdApi.User) {
val device = app.settings.getCurrentSharingDevice()
if (device != null) {
chatsShareInfo.forEach { (_, shareInfo) ->
if (shareInfo.shouldSendViaBotMessage) {
app.telegramHelper.sendViaBotLocationMessage(osmandBot.id, shareInfo, location, device,app.settings.shareTypeValue)
shareInfo.shouldSendViaBotMessage = false
}
}
}
} }
private fun refreshNotification() { private fun refreshNotification() {

View file

@ -8,10 +8,11 @@ import net.osmand.aidl.map.ALatLon
import net.osmand.aidl.maplayer.point.AMapPoint import net.osmand.aidl.maplayer.point.AMapPoint
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication 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.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.utils.AndroidUtils 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 org.drinkless.td.libcore.telegram.TdApi
import java.io.File import java.io.File
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -62,7 +63,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
execOsmandApi { execOsmandApi {
val messages = telegramHelper.getMessages() val messages = telegramHelper.getMessages()
for (message in messages) { for (message in messages) {
val date = telegramHelper.getLastUpdatedTime(message) val date = OsmandLocationUtils.getLastUpdatedTime(message)
val messageShowingTime = System.currentTimeMillis() / 1000 - date val messageShowingTime = System.currentTimeMillis() / 1000 - date
if (messageShowingTime > app.settings.locHistoryTime) { if (messageShowingTime > app.settings.locHistoryTime) {
removeMapPoint(message.chatId, message) removeMapPoint(message.chatId, message)
@ -78,7 +79,7 @@ class ShowLocationHelper(private val app: TelegramApplication) {
val chatId = message.chatId val chatId = message.chatId
val chatTitle = telegramHelper.getChat(message.chatId)?.title val chatTitle = telegramHelper.getChat(message.chatId)?.title
val content = message.content val content = message.content
val date = telegramHelper.getLastUpdatedTime(message) val date = OsmandLocationUtils.getLastUpdatedTime(message)
val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime
if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserTextLocation && content.isValid()))) { if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserTextLocation && content.isValid()))) {
var userName = "" var userName = ""

View file

@ -3,15 +3,20 @@ package net.osmand.telegram.helpers
import android.text.TextUtils import android.text.TextUtils
import net.osmand.Location import net.osmand.Location
import net.osmand.PlatformUtil import net.osmand.PlatformUtil
import net.osmand.telegram.SHARE_TYPE_MAP import net.osmand.telegram.*
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.helpers.TelegramHelper.TelegramAuthenticationParameterType.* import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.*
import net.osmand.telegram.utils.BASE_SHARING_URL import net.osmand.telegram.utils.BASE_SHARING_URL
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR
import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_EXT 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
import org.drinkless.td.libcore.telegram.Client.ResultHandler import org.drinkless.td.libcore.telegram.Client.ResultHandler
import org.drinkless.td.libcore.telegram.TdApi 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 IGNORED_ERROR_CODE = 406
private const val MESSAGE_CANNOT_BE_EDITED_ERROR_CODE = 5 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 // min and max values for the Telegram API
const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61 const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61
const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day 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 getCurrentUser() = currentUser
fun getCurrentUserId() = currentUser?.id ?: -1
fun getUserMessage(user: TdApi.User) = fun getUserMessage(user: TdApi.User) =
usersLocationMessages.values.firstOrNull { it.senderUserId == user.id } 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 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 isPrivateChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypePrivate
fun isSecretChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypeSecret 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 isInit() = client != null && haveAuthorization
fun getUserPhotoPath(user: TdApi.User?) = when { fun getUserPhotoPath(user: TdApi.User?) = when {
@ -564,7 +542,7 @@ class TelegramHelper private constructor() {
resultArticles.forEach { resultArticles.forEach {
client?.send(TdApi.SendInlineQueryResultMessage(shareInfo.chatId, 0, true, client?.send(TdApi.SendInlineQueryResultMessage(shareInfo.chatId, 0, true,
true, inlineQueryResults.inlineQueryId, it.id)) { obj -> 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) { private fun addNewMessage(message: TdApi.Message) {
lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate)) lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate))
if (message.isAppropriate()) { if (message.isAppropriate()) {
log.debug("addNewMessage: $message") log.debug("addNewMessage: ${message.id}")
val fromBot = isOsmAndBot(message.senderUserId) val fromBot = isOsmAndBot(message.senderUserId)
val viaBot = isOsmAndBot(message.viaBotUserId) val viaBot = isOsmAndBot(message.viaBotUserId)
val oldContent = message.content val oldContent = message.content
@ -753,7 +731,7 @@ class TelegramHelper private constructor() {
usersLocationMessages[message.id] = message usersLocationMessages[message.id] = message
} }
incomingMessagesListeners.forEach { incomingMessagesListeners.forEach {
if (!hasNewerMessage || it is SavingTracksDbHelper) { if (!hasNewerMessage || it is TelegramService) {
it.onReceiveChatLocationMessages(message.chatId, message) it.onReceiveChatLocationMessages(message.chatId, message)
} }
} }
@ -779,7 +757,7 @@ class TelegramHelper private constructor() {
} }
} }
} else if (sameSender && isUserLocationMessage(message) && isUserLocationMessage(newMessage) } 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() iterator.remove()
} }
} }
@ -811,7 +789,7 @@ class TelegramHelper private constructor() {
if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) { if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) {
client?.send( client?.send(
TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, null)) { obj -> TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, null)) { obj ->
handleMapLocationMessageUpdate(obj, shareInfo) handleMapLocationMessageUpdate(obj, shareInfo, null)
} }
} }
needRefreshActiveLiveLocationMessages = true needRefreshActiveLiveLocationMessages = true
@ -856,7 +834,7 @@ class TelegramHelper private constructor() {
private fun recreateLiveLocationMessage( private fun recreateLiveLocationMessage(
shareInfo: TelegramSettings.ShareChatInfo, shareInfo: TelegramSettings.ShareChatInfo,
content: TdApi.InputMessageContent content: TdApi.InputMessageContent,locationMessage: LocationMessages.LocationMessage?
) { ) {
if (shareInfo.chatId != -1L) { if (shareInfo.chatId != -1L) {
val array = LongArray(1) val array = LongArray(1)
@ -869,7 +847,7 @@ class TelegramHelper private constructor() {
log.debug("recreateLiveLocationMessage - ${array[0]}") log.debug("recreateLiveLocationMessage - ${array[0]}")
client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj -> client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj ->
when (obj.constructor) { when (obj.constructor) {
TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content) TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content,locationMessage)
TdApi.Error.CONSTRUCTOR -> { TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error val error = obj as TdApi.Error
if (error.code != IGNORED_ERROR_CODE) { if (error.code != IGNORED_ERROR_CODE) {
@ -886,12 +864,12 @@ class TelegramHelper private constructor() {
needRefreshActiveLiveLocationMessages = true 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 needRefreshActiveLiveLocationMessages = true
log.debug("sendNewLiveLocationMessage") log.debug("sendNewLiveLocationMessage")
client?.send( client?.send(
TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj -> 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}") log.debug("sendLiveLocationImpl - $msgId pendingMapMessage ${shareInfo.pendingMapMessage}")
if (msgId != -1L) { if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousMapMessage) { if (shareInfo.shouldDeletePreviousMapMessage) {
recreateLiveLocationMessage(shareInfo, content) recreateLiveLocationMessage(shareInfo, content, null)
shareInfo.shouldDeletePreviousMapMessage = false shareInfo.shouldDeletePreviousMapMessage = false
shareInfo.currentMapMessageId = -1 shareInfo.currentMapMessageId = -1
} else { } else {
log.debug("EditMessageLiveLocation - $msgId") log.debug("EditMessageLiveLocation - $msgId")
client?.send( client?.send(
TdApi.EditMessageLiveLocation(chatId, msgId, null, location)) { obj -> 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) { } else if (!shareInfo.pendingMapMessage || shareInfo.pendingMapMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) {
sendNewLiveLocationMessage(shareInfo, content) sendNewLiveLocationMessage(shareInfo, content, null)
} }
} }
} }
fun sendLiveLocationText(chatsShareInfo: Map<Long, TelegramSettings.ShareChatInfo>, location: Location) { fun sendLiveLocationMap(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.LocationMessage) {
chatsShareInfo.forEach { (chatId, shareInfo) -> val location = TdApi.Location(locationMessage.lat, locationMessage.lon)
if (shareInfo.getChatLiveMessageExpireTime() <= 0) { val livePeriod = if (shareInfo.currentMessageLimit > (shareInfo.start + MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC)) {
return@forEach MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC
} else {
shareInfo.livePeriod.toInt()
} }
val content = TdApi.InputMessageLocation(location, livePeriod)
val msgId = shareInfo.currentMapMessageId
val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendMapMessageTime)
log.debug("sendLiveLocationImpl - $msgId pendingMapMessage ${shareInfo.pendingMapMessage}")
if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousMapMessage) {
recreateLiveLocationMessage(shareInfo, content, 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)
}
}
} 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 val msgId = shareInfo.currentTextMessageId
if (msgId == -1L) { if (msgId == -1L) {
shareInfo.updateTextMessageId = 1 shareInfo.updateTextMessageId = 1
} }
val content = getTextMessageContent(shareInfo.updateTextMessageId, location) val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location)
val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendTextMessageTime) val timeAfterLastSendMessage = ((System.currentTimeMillis() / 1000) - shareInfo.lastSendTextMessageTime)
log.debug("sendLiveLocationText - $msgId pendingMapMessage ${shareInfo.pendingTextMessage}") log.debug("sendLiveLocationText - $msgId pendingMapMessage ${shareInfo.pendingTextMessage}")
if (msgId != -1L) { if (msgId != -1L) {
if (shareInfo.shouldDeletePreviousTextMessage) { if (shareInfo.shouldDeletePreviousTextMessage) {
recreateLiveLocationMessage(shareInfo, content) recreateLiveLocationMessage(shareInfo, content, location)
shareInfo.shouldDeletePreviousTextMessage = false shareInfo.shouldDeletePreviousTextMessage = false
} else { } else {
client?.send(TdApi.EditMessageText(chatId, msgId, null, content)) { obj -> client?.send(TdApi.EditMessageText(shareInfo.chatId, msgId, null, content)) { obj ->
handleTextLocationMessageUpdate(obj, shareInfo) handleTextLocationMessageUpdate(obj, shareInfo, location)
} }
} }
} else if (!shareInfo.pendingTextMessage || shareInfo.pendingTextMessage && timeAfterLastSendMessage > SEND_NEW_MESSAGE_INTERVAL_SEC) { } else {
client?.send(TdApi.SendMessage(chatId, 0, false, false, null, content)) { obj -> if (!shareInfo.pendingTextMessage) {
handleTextLocationMessageUpdate(obj, shareInfo) 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) { when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> { TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error val error = obj as TdApi.Error
@ -978,6 +983,7 @@ class TelegramHelper private constructor() {
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
shareInfo.hasSharingError = true shareInfo.hasSharingError = true
needRefreshActiveLiveLocationMessages = true needRefreshActiveLiveLocationMessages = true
location?.status = LocationMessages.LocationMessage.STATUS_ERROR
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(-1, "Map location message ${obj.id} failed to send") 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 -> { obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> {
shareInfo.pendingMapMessage = true shareInfo.pendingMapMessage = true
shareInfo.lastSendMapMessageTime = obj.date shareInfo.lastSendMapMessageTime = obj.date
location?.status = LocationMessages.LocationMessage.STATUS_PENDING
log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending") log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending")
} }
else -> { else -> {
shareInfo.hasSharingError = false shareInfo.hasSharingError = false
shareInfo.bufferedMessages--
location?.messageId = obj.id
location?.status = LocationMessages.LocationMessage.STATUS_SENT
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj)) 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) { when (obj.constructor) {
TdApi.Error.CONSTRUCTOR -> { TdApi.Error.CONSTRUCTOR -> {
val error = obj as TdApi.Error val error = obj as TdApi.Error
@ -1017,7 +1027,9 @@ class TelegramHelper private constructor() {
when { when {
obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> {
shareInfo.hasSharingError = true shareInfo.hasSharingError = true
shareInfo.bufferedMessages--
needRefreshActiveLiveLocationMessages = true needRefreshActiveLiveLocationMessages = true
location?.status = LocationMessages.LocationMessage.STATUS_ERROR
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onSendLiveLocationError(-1, "Text location message ${obj.id} failed to send") 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 -> { obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> {
shareInfo.pendingTextMessage = true shareInfo.pendingTextMessage = true
shareInfo.lastSendTextMessageTime = obj.date shareInfo.lastSendTextMessageTime = obj.date
location?.status = LocationMessages.LocationMessage.STATUS_PENDING
log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending") log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending")
} }
else -> { else -> {
shareInfo.hasSharingError = false shareInfo.hasSharingError = false
shareInfo.bufferedMessages--
location?.messageId = obj.id
location?.status = LocationMessages.LocationMessage.STATUS_SENT
outgoingMessagesListeners.forEach { outgoingMessagesListeners.forEach {
it.onUpdateMessages(listOf(obj)) 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<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.latitude}&lon=${location.longitude}")))
builder.append("$locationMessage\n")
if (location.hasAltitude() && location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.hasSpeed() && location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hasAccuracy() && location.accuracy != 0.0f && location.speed == 0.0f) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.accuracy.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
} else {
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
}
val textMessage = builder.toString().trim()
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
}
fun logout(): Boolean { fun logout(): Boolean {
return if (libraryLoaded) { return if (libraryLoaded) {
haveAuthorization = false 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<OrderedChat> { class OrderedChat internal constructor(internal val order: Long, internal val chatId: Long, internal val isChannel: Boolean) : Comparable<OrderedChat> {
override fun compareTo(other: OrderedChat): Int { override fun compareTo(other: OrderedChat): Int {

View file

@ -6,8 +6,9 @@ import android.widget.ImageView
import net.osmand.data.LatLon import net.osmand.data.LatLon
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.TelegramApplication import net.osmand.telegram.TelegramApplication
import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.telegram.helpers.TelegramHelper.MessageUserTextLocation import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserTextLocation
import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation
import net.osmand.telegram.utils.GPXUtilities import net.osmand.telegram.utils.GPXUtilities
import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi
@ -65,7 +66,7 @@ object TelegramUiHelper {
val user = helper.getUser(userId) val user = helper.getUser(userId)
val message = messages.firstOrNull { it.viaBotUserId == 0 } val message = messages.firstOrNull { it.viaBotUserId == 0 }
if (message != null) { if (message != null) {
res.lastUpdated = helper.getLastUpdatedTime(message) res.lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
val content = message.content val content = message.content
if (content is TdApi.MessageLocation) { if (content is TdApi.MessageLocation) {
res.latLon = LatLon(content.location.latitude, content.location.longitude) res.latLon = LatLon(content.location.latitude, content.location.longitude)
@ -172,7 +173,7 @@ object TelegramUiHelper {
grayscalePhotoPath = helper.getUserGreyPhotoPath(user) grayscalePhotoPath = helper.getUserGreyPhotoPath(user)
placeholderId = R.drawable.img_user_picture placeholderId = R.drawable.img_user_picture
userId = message.senderUserId userId = message.senderUserId
lastUpdated = helper.getLastUpdatedTime(message) lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
} }
} }
@ -225,7 +226,7 @@ object TelegramUiHelper {
userId = message.senderUserId userId = message.senderUserId
privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat) privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat)
chatWithBot = helper.isBot(userId) chatWithBot = helper.isBot(userId)
lastUpdated = helper.getLastUpdatedTime(message) lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message)
} }
} }

View file

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

View file

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

View file

@ -714,6 +714,18 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
OsmandFormatter.getFormattedDuration(context!!, expiresIn, true) 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,6 +763,8 @@ class MyLocationTabFragment : Fragment(), TelegramListener {
val stopSharingDescr: TextView? = view.findViewById(R.id.stop_in) val stopSharingDescr: TextView? = view.findViewById(R.id.stop_in)
val stopSharingFirstPart: TextView? = view.findViewById(R.id.ending_in_first_part) val stopSharingFirstPart: TextView? = view.findViewById(R.id.ending_in_first_part)
val stopSharingSecondPart: TextView? = view.findViewById(R.id.ending_in_second_part) val stopSharingSecondPart: TextView? = view.findViewById(R.id.ending_in_second_part)
val gpsPointsCollected: TextView? = view.findViewById(R.id.gps_points_collected)
val gpsPointsSent: TextView? = view.findViewById(R.id.gps_points_sent)
} }
} }

View file

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

View file

@ -4,6 +4,7 @@ import android.app.DatePickerDialog
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.support.annotation.DrawableRes import android.support.annotation.DrawableRes
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager 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.helpers.TelegramUiHelper.ListItem
import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.OsmandFormatter import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandLocationUtils
import java.util.* import java.util.*
@ -45,6 +46,8 @@ class TimelineTabFragment : Fragment() {
private var start = 0L private var start = 0L
private var end = 0L private var end = 0L
private var updateEnable: Boolean = false
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -93,6 +96,17 @@ class TimelineTabFragment : Fragment() {
return mainView return mainView
} }
override fun onResume() {
super.onResume()
updateEnable = true
startHandler()
}
override fun onPause() {
super.onPause()
updateEnable = false
}
private fun setupBtnTextColor(textView: TextView) { private fun setupBtnTextColor(textView: TextView) {
textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light)) textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light))
} }
@ -162,32 +176,37 @@ class TimelineTabFragment : Fragment() {
return normal return normal
} }
private fun startHandler() {
val updateAdapter = Handler()
updateAdapter.postDelayed({
if (updateEnable) {
updateList()
startHandler()
}
}, ADAPTER_UPDATE_INTERVAL_MIL)
}
private fun updateList() { private fun updateList() {
val res = mutableListOf<ListItem>() val res = mutableListOf<ListItem>()
val s = System.currentTimeMillis()
log.debug("updateList $s")
val ignoredUsersIds = ArrayList<Int>() val ignoredUsersIds = ArrayList<Int>()
val currentUserId = telegramHelper.getCurrentUser()?.id val currentUserId = telegramHelper.getCurrentUser()?.id
if (currentUserId != null) { if (currentUserId != null) {
val currentUserGpx:GPXUtilities.GPXFile? = app.savingTracksDbHelper.collectRecordedDataForUser(currentUserId, 0, start, end) val locationMessages = app.locationMessages.collectRecordedDataForUser(currentUserId, 0, start, end)
if (currentUserGpx != null) { OsmandLocationUtils.convertLocationMessagesToGpxFiles(locationMessages, false).forEach {
TelegramUiHelper.gpxToChatItem(telegramHelper, currentUserGpx, true)?.also { TelegramUiHelper.gpxToChatItem(telegramHelper, it, true)?.also { chatItem ->
res.add(it) res.add(chatItem)
} }
} }
ignoredUsersIds.add(currentUserId) ignoredUsersIds.add(currentUserId)
} }
val gpxFiles = app.savingTracksDbHelper.collectRecordedDataForUsers(start, end, ignoredUsersIds) val locationMessages = app.locationMessages.collectRecordedDataForUsers(start, end, ignoredUsersIds)
val e = System.currentTimeMillis() OsmandLocationUtils.convertLocationMessagesToGpxFiles(locationMessages).forEach {
gpxFiles.forEach {
TelegramUiHelper.gpxToChatItem(telegramHelper, it,false)?.also { chatItem -> TelegramUiHelper.gpxToChatItem(telegramHelper, it,false)?.also { chatItem ->
res.add(chatItem) res.add(chatItem)
} }
} }
adapter.items = sortAdapterItems(res) adapter.items = sortAdapterItems(res)
log.debug("updateList $s dif: ${e - s}")
} }
private fun sortAdapterItems(list: MutableList<ListItem>): MutableList<ListItem> { private fun sortAdapterItems(list: MutableList<ListItem>): MutableList<ListItem> {
@ -269,4 +288,8 @@ class TimelineTabFragment : Fragment() {
val bottomDivider: View? = view.findViewById(R.id.bottom_divider) val bottomDivider: View? = view.findViewById(R.id.bottom_divider)
} }
} }
companion object {
private const val ADAPTER_UPDATE_INTERVAL_MIL = 15 * 1000L // 15 sec
}
} }

View file

@ -21,11 +21,11 @@ import net.osmand.PlatformUtil
import net.osmand.aidl.gpx.AGpxBitmap import net.osmand.aidl.gpx.AGpxBitmap
import net.osmand.telegram.R import net.osmand.telegram.R
import net.osmand.telegram.helpers.OsmandAidlHelper import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.SavingTracksDbHelper
import net.osmand.telegram.helpers.TelegramUiHelper import net.osmand.telegram.helpers.TelegramUiHelper
import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.GPXUtilities import net.osmand.telegram.utils.GPXUtilities
import net.osmand.telegram.utils.OsmandFormatter import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandLocationUtils
import net.osmand.util.Algorithms import net.osmand.util.Algorithms
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -63,7 +63,6 @@ class UserGpxInfoFragment : BaseDialogFragment() {
readFromBundle(savedInstanceState ?: arguments) readFromBundle(savedInstanceState ?: arguments)
val userId = gpxFile.userId val userId = gpxFile.userId
val chatId = gpxFile.chatId
val user = app.telegramHelper.getUser(userId) val user = app.telegramHelper.getUser(userId)
if (user != null) { if (user != null) {
@ -134,12 +133,13 @@ class UserGpxInfoFragment : BaseDialogFragment() {
openGpx(gpx.path) openGpx(gpx.path)
} else { } else {
saveCurrentGpxToFile(object : saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener { OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) { override fun onSavingGpxFinish(path: String) {
openGpx(path) openGpx(path)
} }
override fun onSavingGpxError(warnings: MutableList<String>?) { override fun onSavingGpxError(warnings: List<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
} }
}) })
@ -156,12 +156,12 @@ class UserGpxInfoFragment : BaseDialogFragment() {
(activity as MainActivity).shareGpx(gpx.path) (activity as MainActivity).shareGpx(gpx.path)
} else { } else {
saveCurrentGpxToFile(object : saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener { OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) { override fun onSavingGpxFinish(path: String) {
(activity as MainActivity).shareGpx(path) (activity as MainActivity).shareGpx(path)
} }
override fun onSavingGpxError(warnings: MutableList<String>?) { override fun onSavingGpxError(warnings: List<String>?) {
Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show()
} }
}) })
@ -189,8 +189,10 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
} }
private fun saveCurrentGpxToFile(listener: SavingTracksDbHelper.SaveGpxListener) { private fun saveCurrentGpxToFile(listener: OsmandLocationUtils.SaveGpxListener) {
app.savingTracksDbHelper.saveGpx(listener, app.getExternalFilesDir(null), gpxFile) if (!gpxFile.isEmpty) {
OsmandLocationUtils.saveGpx(app, listener, app.getExternalFilesDir(null)!!, gpxFile)
}
} }
private fun readFromBundle(bundle: Bundle?) { private fun readFromBundle(bundle: Bundle?) {
@ -205,7 +207,14 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
private fun updateGpxInfo() { 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() updateGPXStatisticRow()
updateDateAndTimeButtons() updateDateAndTimeButtons()
updateGPXMap() updateGPXMap()
@ -229,7 +238,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
private fun updateGPXMap() { private fun updateGPXMap() {
saveCurrentGpxToFile(object : saveCurrentGpxToFile(object :
SavingTracksDbHelper.SaveGpxListener { OsmandLocationUtils.SaveGpxListener {
override fun onSavingGpxFinish(path: String) { override fun onSavingGpxFinish(path: String) {
val mgr = activity?.getSystemService(Context.WINDOW_SERVICE) val mgr = activity?.getSystemService(Context.WINDOW_SERVICE)
if (mgr != null) { if (mgr != null) {
@ -242,7 +251,7 @@ class UserGpxInfoFragment : BaseDialogFragment() {
} }
} }
override fun onSavingGpxError(warnings: MutableList<String>?) { override fun onSavingGpxError(warnings: List<String>?) {
log.debug("onSavingGpxError ${warnings?.firstOrNull()}") log.debug("onSavingGpxError ${warnings?.firstOrNull()}")
} }
}) })

View file

@ -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<TdApi.TextEntity>()
val builder = StringBuilder()
val locationMessage = formatLocation(location)
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
builder.append("$USER_TEXT_LOCATION_TITLE\n")
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(LOCATION_PREFIX)
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}")))
builder.append("$locationMessage\n")
if (location.altitude != 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
}
if (location.speed > 0) {
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
}
if (location.hdop != 0.0 && location.speed == 0.0) {
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt()))
}
if (updateId == 0) {
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.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<LocationMessages.LocationMessage>, newGpxPerChat: Boolean = true): List<GPXUtilities.GPXFile> {
val dataTracks = ArrayList<GPXUtilities.GPXFile>()
var previousTime: Long = -1
var previousChatId: Long = -1
var previousUserId = -1
var segment: GPXUtilities.TrkSegment? = null
var track: GPXUtilities.Track? = null
var gpx: GPXUtilities.GPXFile? = null
items.forEach {
val userId = it.userId
val chatId = it.chatId
val time = it.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<Void, Void, List<String>>() {
override fun doInBackground(vararg params: Void): List<String> {
val warnings = ArrayList<String>()
dir.mkdirs()
if (dir.parentFile.canWrite()) {
if (dir.exists()) {
// save file
var fout = File(dir, "$userId.gpx")
if (!gpxFile.isEmpty) {
val pt = gpxFile.findPointToShow()
val user = app.telegramHelper.getUser(pt!!.userId)
val fileName: String
fileName = if (user != null) {
(TelegramUiHelper.getUserName(user) + "_" + SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(Date(pt.time)))
} else {
userId.toString() + "_" + SimpleDateFormat("yyyy-MM-dd_HH-mm_EEE", Locale.US).format(Date(pt.time))
}
fout = File(dir, "$fileName.gpx")
var ind = 1
while (fout.exists()) {
fout = File(dir, "${fileName}_${++ind}.gpx")
}
}
val warn = GPXUtilities.writeGpxFile(fout, gpxFile, app)
if (warn != null) {
warnings.add(warn)
return warnings
}
}
}
return warnings
}
override fun onPostExecute(warnings: List<String>?) {
if (listener != null) {
if (warnings != null && warnings.isEmpty()) {
listener.onSavingGpxFinish(gpxFile.path)
} else {
listener.onSavingGpxError(warnings)
}
}
}
}
interface SaveGpxListener {
fun onSavingGpxFinish(path: String)
fun onSavingGpxError(warnings: List<String>?)
}
}