OsmAnd/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt
2019-02-14 18:00:32 +02:00

1022 lines
No EOL
34 KiB
Kotlin

package net.osmand.telegram
import android.content.Context
import android.location.LocationManager
import android.support.annotation.ColorRes
import android.support.annotation.DrawableRes
import android.support.annotation.StringRes
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import net.osmand.PlatformUtil
import net.osmand.telegram.helpers.OsmandAidlHelper
import net.osmand.telegram.helpers.TelegramHelper
import net.osmand.telegram.utils.AndroidUtils
import net.osmand.telegram.utils.OsmandApiUtils
import net.osmand.telegram.utils.OsmandFormatter
import net.osmand.telegram.utils.OsmandFormatter.MetricsConstants
import net.osmand.telegram.utils.OsmandFormatter.SpeedConstants
import net.osmand.telegram.utils.OsmandLocationUtils
import org.drinkless.td.libcore.telegram.TdApi
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
val ADDITIONAL_ACTIVE_TIME_VALUES_SEC = listOf(15 * 60L, 30 * 60L, 60 * 60L, 180 * 60L)
const val SHARE_DEVICES_KEY = "devices"
private val SEND_MY_LOC_VALUES_SEC =
listOf(1L, 2L, 3L, 5L, 10L, 15L, 30L, 60L, 90L, 2 * 60L, 3 * 60L, 5 * 60L)
private val STALE_LOC_VALUES_SEC =
listOf(1 * 60L, 2 * 60L, 5 * 60L, 10 * 60L, 15 * 60L, 30 * 60L, 60 * 60L)
private val LOC_HISTORY_VALUES_SEC = listOf(
5 * 60L,
15 * 60L,
30 * 60L,
1 * 60 * 60L,
2 * 60 * 60L,
3 * 60 * 60L,
5 * 60 * 60L,
8 * 60 * 60L,
12 * 60 * 60L,
24 * 60 * 60L
)
const val SHARE_TYPE_MAP = "Map"
const val SHARE_TYPE_TEXT = "Text"
const val SHARE_TYPE_MAP_AND_TEXT = "Map_and_text"
private val SHARE_TYPE_VALUES = listOf(SHARE_TYPE_MAP, SHARE_TYPE_TEXT, SHARE_TYPE_MAP_AND_TEXT)
private const val SEND_MY_LOC_DEFAULT_INDEX = 6
private const val STALE_LOC_DEFAULT_INDEX = 0
private const val LOC_HISTORY_DEFAULT_INDEX = 7
private const val SHARE_TYPE_DEFAULT_INDEX = 2
private const val SETTINGS_NAME = "osmand_telegram_settings"
private const val SHARE_LOCATION_CHATS_KEY = "share_location_chats"
private const val HIDDEN_ON_MAP_CHATS_KEY = "hidden_on_map_chats"
private const val SHARING_MODE_KEY = "current_sharing_mode"
private const val METRICS_CONSTANTS_KEY = "metrics_constants"
private const val SPEED_CONSTANTS_KEY = "speed_constants"
private const val SEND_MY_LOC_INTERVAL_KEY = "send_my_loc_interval"
private const val STALE_LOC_TIME_KEY = "stale_loc_time"
private const val LOC_HISTORY_TIME_KEY = "loc_history_time"
private const val SHARE_TYPE_KEY = "share_type"
private const val APP_TO_CONNECT_PACKAGE_KEY = "app_to_connect_package"
private const val DEFAULT_VISIBLE_TIME_SECONDS = 60 * 60L // 1 hour
private const val TITLES_REPLACED_WITH_IDS = "changed_to_chat_id"
private const val LIVE_NOW_SORT_TYPE_KEY = "live_now_sort_type"
private const val SHARE_CHATS_INFO_KEY = "share_chats_info"
private const val BATTERY_OPTIMISATION_ASKED = "battery_optimisation_asked"
private const val MONITORING_ENABLED = "monitoring_enabled"
private const val SHOW_GPS_POINTS = "show_gps_points"
private const val SHARING_INITIALIZATION_TIME = 60 * 2L // 2 minutes
private const val WAITING_TDLIB_TIME = 30 // 2 seconds
private const val GPS_UPDATE_EXPIRED_TIME = 60 * 3L // 3 minutes
class TelegramSettings(private val app: TelegramApplication) {
private val log = PlatformUtil.getLog(TelegramSettings::class.java)
private var shareChatsInfo = ConcurrentHashMap<Long, ShareChatInfo>()
private var hiddenOnMapChats: Set<Long> = emptySet()
private var shareDevices: Set<DeviceBot> = emptySet()
var sharingStatusChanges = ConcurrentLinkedQueue<SharingStatus>()
var currentSharingMode = ""
private set
var metricsConstants = MetricsConstants.KILOMETERS_AND_METERS
var speedConstants = SpeedConstants.KILOMETERS_PER_HOUR
var sendMyLocInterval = SEND_MY_LOC_VALUES_SEC[SEND_MY_LOC_DEFAULT_INDEX]
var staleLocTime = STALE_LOC_VALUES_SEC[STALE_LOC_DEFAULT_INDEX]
var locHistoryTime = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX]
var shareTypeValue = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX]
var appToConnectPackage = ""
private set
var liveNowSortType = LiveNowSortType.SORT_BY_DISTANCE
val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref(), ShareTypePref())
var batteryOptimisationAsked = false
var monitoringEnabled = false
var showGpsPoints = false
init {
updatePrefs()
read()
}
fun hasAnyChatToShareLocation() = shareChatsInfo.isNotEmpty()
fun isSharingLocationToChat(chatId: Long) = shareChatsInfo.containsKey(chatId)
fun isSharingLocationToUser(userId: Int) = shareChatsInfo.values.any { it.userId == userId }
fun hasAnyChatToShowOnMap() = !hiddenOnMapChats.containsAll(getLiveNowChats())
fun isShowingChatOnMap(chatId: Long) = !hiddenOnMapChats.contains(chatId)
fun removeNonexistingChats(presentChatIds: List<Long>) {
val hiddenChats = hiddenOnMapChats.toMutableList()
hiddenChats.intersect(presentChatIds)
hiddenOnMapChats = hiddenChats.toHashSet()
shareChatsInfo = ConcurrentHashMap(shareChatsInfo.filter { (key, _) -> presentChatIds.contains(key) })
}
fun shareLocationToChat(
chatId: Long,
share: Boolean,
livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS,
addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
) {
if (share) {
var shareChatInfo = shareChatsInfo[chatId]
if (shareChatInfo == null) {
shareChatInfo = ShareChatInfo()
}
val chat = app.telegramHelper.getChat(chatId)
if (chat != null && (chat.type is TdApi.ChatTypePrivate || chat.type is TdApi.ChatTypeSecret)) {
shareChatInfo.userId = app.telegramHelper.getUserIdFromChatType(chat.type)
}
shareChatInfo.chatId = chatId
updateChatShareInfo(shareChatInfo, livePeriod, addActiveTime)
shareChatsInfo[chatId] = shareChatInfo
} else {
shareChatsInfo.remove(chatId)
}
}
fun shareLocationToUser(
userId: Int,
livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS,
addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
) {
val shareChatInfo = ShareChatInfo()
shareChatInfo.userId = userId
updateChatShareInfo(shareChatInfo, livePeriod, addActiveTime)
app.telegramHelper.createPrivateChatWithUser(userId, shareChatInfo, shareChatsInfo)
}
fun updateShareDevices(list: List<DeviceBot>) {
shareDevices = list.toHashSet()
}
fun addShareDevice(device: DeviceBot) {
val devices = shareDevices.toMutableList()
devices.add(device)
shareDevices = devices.toHashSet()
}
fun updateCurrentSharingMode(sharingMode: String) {
if (currentSharingMode != sharingMode) {
shareChatsInfo.forEach { (_, shareInfo) ->
shareInfo.shouldSendViaBotTextMessage = true
shareInfo.shouldSendViaBotMapMessage = true
}
prepareForSharingNewMessages()
}
currentSharingMode = sharingMode
}
fun prepareForSharingNewMessages() {
shareChatsInfo.forEach { (_, shareInfo) ->
prepareForSharingNewMessages(shareInfo)
}
}
fun prepareForSharingNewMessages(chatsIds: List<Long>) {
chatsIds.forEach {
shareChatsInfo[it]?.also { shareInfo ->
prepareForSharingNewMessages(shareInfo)
}
}
}
fun prepareForSharingNewMessages(shareInfo: ShareChatInfo) {
shareInfo.pendingTdLibText = 0
shareInfo.pendingTdLibMap = 0
shareInfo.currentTextMessageId = -1L
shareInfo.currentMapMessageId = -1L
shareInfo.pendingTextMessage = false
shareInfo.pendingMapMessage = false
}
fun getChatLivePeriod(chatId: Long) = shareChatsInfo[chatId]?.livePeriod
fun getChatsShareInfo() = shareChatsInfo
fun getShareDevices() = shareDevices
fun containsShareDeviceWithName(name: String): Boolean {
shareDevices.forEach {
if (it.deviceName == name) {
return true
}
}
return false
}
fun getCurrentSharingDevice() = shareDevices.singleOrNull { it.externalId == currentSharingMode }
fun getLastSuccessfulSendTime(): Long {
val lastSuccessTextSend = shareChatsInfo.values.maxBy { it.lastTextSuccessfulSendTime }?.lastTextSuccessfulSendTime ?: -1
val lastSuccessMapSend = shareChatsInfo.values.maxBy { it.lastMapSuccessfulSendTime }?.lastMapSuccessfulSendTime ?: -1
return Math.max(lastSuccessTextSend, lastSuccessMapSend)
}
fun stopSharingLocationToChats() {
shareChatsInfo.clear()
}
fun showChatOnMap(chatId: Long, show: Boolean) {
val hiddenChats = hiddenOnMapChats.toMutableList()
if (show) {
hiddenChats.remove(chatId)
} else {
hiddenChats.add(chatId)
}
hiddenOnMapChats = hiddenChats.toHashSet()
}
fun getShareLocationChats() = shareChatsInfo.keys
fun getShowOnMapChats() = getLiveNowChats().minus(hiddenOnMapChats)
fun getShowOnMapChatsCount() = getShowOnMapChats().size
fun clear() {
stopSharingLocationToChats()
app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE).edit().clear().apply()
}
fun updateAppToConnect(appToConnectPackage: String) {
app.showLocationHelper.stopShowingLocation()
this.appToConnectPackage = appToConnectPackage
app.osmandAidlHelper.reconnectOsmand()
}
fun updateShareInfo(message: TdApi.Message) {
val shareInfo = shareChatsInfo[message.chatId]
val content = message.content
val isOsmAndBot = app.telegramHelper.isOsmAndBot(OsmandLocationUtils.getSenderMessageId(message)) || app.telegramHelper.isOsmAndBot(message.viaBotUserId)
if (shareInfo != null) {
when (content) {
is TdApi.MessageLocation -> {
val state = message.sendingState
if (state != null) {
if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) {
shareInfo.pendingMapMessage = true
log.debug("updateShareInfo MAP ${message.id} MessageSendingStatePending")
shareInfo.oldMapMessageId = message.id
if (isOsmAndBot) {
shareInfo.shouldSendViaBotMapMessage = false
}
} else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) {
shareInfo.hasSharingError = true
shareInfo.pendingMapMessage = false
log.debug("updateShareInfo MAP ${message.id} MessageSendingStateFailed")
}
} else {
shareInfo.currentMapMessageId = message.id
shareInfo.pendingMapMessage = false
shareInfo.lastMapSuccessfulSendTime = System.currentTimeMillis() / 1000
if (!isOsmAndBot) {
shareInfo.pendingTdLibMap--
if (shareTypeValue == SHARE_TYPE_MAP) {
shareInfo.sentMessages++
}
} else {
shareInfo.shouldSendViaBotMapMessage = false
}
log.debug("updateShareInfo MAP ${message.id} SUCCESS pendingTdLibMap: ${shareInfo.pendingTdLibMap}")
}
}
is TdApi.MessageText -> {
val state = message.sendingState
if (state != null) {
if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) {
log.debug("updateShareInfo TEXT ${message.id} MessageSendingStatePending")
shareInfo.pendingTextMessage = true
shareInfo.oldTextMessageId = message.id
if (isOsmAndBot) {
shareInfo.shouldSendViaBotTextMessage = false
}
} else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) {
log.debug("updateShareInfo TEXT ${message.id} MessageSendingStateFailed")
shareInfo.hasSharingError = true
shareInfo.pendingTextMessage = false
}
} else {
shareInfo.currentTextMessageId = message.id
shareInfo.updateTextMessageId++
shareInfo.pendingTextMessage = false
shareInfo.lastTextSuccessfulSendTime = System.currentTimeMillis() / 1000
if (!isOsmAndBot) {
shareInfo.pendingTdLibText--
shareInfo.sentMessages++
} else {
shareInfo.shouldSendViaBotTextMessage = false
}
log.debug("updateShareInfo TEXT ${message.id} SUCCESS pendingTdLibMap: ${shareInfo.pendingTdLibText}")
}
}
}
}
}
fun updateSharingStatusHistory() {
val newSharingStatus = getNewSharingStatusHistoryItem()
if (sharingStatusChanges.isNotEmpty()) {
val lastSharingStatus = sharingStatusChanges.last()
if (lastSharingStatus.statusType != newSharingStatus.statusType) {
sharingStatusChanges.add(newSharingStatus)
} else {
lastSharingStatus.apply {
statusChangeTime = newSharingStatus.statusChangeTime
locationTime = newSharingStatus.locationTime
chatsIds = newSharingStatus.chatsIds
title = newSharingStatus.title
if (statusType == SharingStatusType.INITIALIZING
&& newSharingStatus.statusType == SharingStatusType.INITIALIZING) {
if (!description.contains(newSharingStatus.description)) {
description = "$description, ${newSharingStatus.description}"
}
} else {
description = newSharingStatus.description
}
}
}
} else {
sharingStatusChanges.add(newSharingStatus)
}
}
private fun updateChatShareInfo(
shareChatInfo: ShareChatInfo,
livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS,
addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
) {
val lp: Long = when {
livePeriod < TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC -> TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()
else -> livePeriod
}
val currentTime = System.currentTimeMillis() / 1000
val user = app.telegramHelper.getCurrentUser()
if (user != null && currentSharingMode != user.id.toString() && shareChatInfo.start == -1L) {
shareChatInfo.shouldSendViaBotTextMessage = true
shareChatInfo.shouldSendViaBotMapMessage = true
}
shareChatInfo.start = currentTime
if (shareChatInfo.livePeriod == -1L) {
shareChatInfo.livePeriod = lp
}
shareChatInfo.userSetLivePeriod = lp
shareChatInfo.userSetLivePeriodStart = currentTime
shareChatInfo.currentMessageLimit = currentTime + Math.min(lp, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong())
shareChatInfo.additionalActiveTime = addActiveTime
}
private fun getNewSharingStatusHistoryItem(): SharingStatus {
return SharingStatus().apply {
statusChangeTime = System.currentTimeMillis()
val lm = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val gpsEnabled = try {
if (lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
val loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER)
val gpsActive = loc != null && ((statusChangeTime - loc.time) / 1000) < GPS_UPDATE_EXPIRED_TIME
val lastSentLocationExpired = ((statusChangeTime - app.shareLocationHelper.lastLocationUpdateTime) / 1000) > GPS_UPDATE_EXPIRED_TIME
(gpsActive || !lastSentLocationExpired)
} else {
false
}
} catch (ex: Exception) {
false
}
var initializing = false
var sendChatsErrors = false
shareChatsInfo.forEach { (_, shareInfo) ->
if (shareInfo.lastTextSuccessfulSendTime == -1L && shareInfo.lastMapSuccessfulSendTime == -1L
&& ((statusChangeTime / 1000 - shareInfo.start) < SHARING_INITIALIZATION_TIME)) {
initializing = true
} else if (shareInfo.hasSharingError
|| (shareInfo.lastSendTextMessageTime - shareInfo.lastTextSuccessfulSendTime > WAITING_TDLIB_TIME)
|| (shareInfo.lastSendMapMessageTime - shareInfo.lastMapSuccessfulSendTime > WAITING_TDLIB_TIME)
) {
sendChatsErrors = true
locationTime = Math.max(shareInfo.lastTextSuccessfulSendTime, shareInfo.lastMapSuccessfulSendTime)
chatsIds.add(shareInfo.chatId)
}
}
if (sendChatsErrors) {
title = app.getString(R.string.not_possible_to_send_to_telegram_chats)
description = app.getString(R.string.last_updated_location)
statusType = SharingStatusType.NOT_POSSIBLE_TO_SENT_TO_CHATS
} else if (!initializing) {
when {
!gpsEnabled -> {
locationTime = app.shareLocationHelper.lastLocationUpdateTime
if (locationTime <= 0) {
locationTime = getLastSuccessfulSendTime()
}
title = app.getString(R.string.no_gps_connection)
description = app.getString(R.string.last_updated_location)
statusType = SharingStatusType.NO_GPS
}
!app.isInternetConnectionAvailable -> {
locationTime = getLastSuccessfulSendTime()
title = app.getString(R.string.no_internet_connection)
description = app.getString(R.string.last_updated_location)
statusType = SharingStatusType.NO_INTERNET
}
else -> {
locationTime = getLastSuccessfulSendTime()
statusType = SharingStatusType.SENDING
if (locationTime == -1L) {
title = app.getString(R.string.sending_location_messages)
description = app.getString(R.string.waiting_for_response_from_telegram)
} else {
title = app.getString(R.string.successfully_sent_and_updated)
description = app.getString(R.string.last_updated_location)
}
}
}
} else {
if (gpsEnabled && app.isInternetConnectionAvailable) {
title = app.getString(R.string.sending_location_messages)
description = app.getString(R.string.waiting_for_response_from_telegram)
statusType = SharingStatusType.SENDING
} else {
title = app.getString(R.string.initializing)
statusType = SharingStatusType.INITIALIZING
if (!gpsEnabled) {
description = app.getString(R.string.searching_for_gps)
} else if (!app.isInternetConnectionAvailable) {
description = app.getString(R.string.connecting_to_the_internet)
}
}
}
}
}
fun onDeleteLiveMessages(chatId: Long, messages: List<Long>) {
val currentMapMessageId = shareChatsInfo[chatId]?.currentMapMessageId
if (messages.contains(currentMapMessageId)) {
shareChatsInfo[chatId]?.currentMapMessageId = -1
shareChatsInfo[chatId]?.shouldSendViaBotMapMessage = true
shareChatsInfo[chatId]?.shouldSendViaBotTextMessage = true
}
val currentTextMessageId = shareChatsInfo[chatId]?.currentTextMessageId
if (messages.contains(currentTextMessageId)) {
shareChatsInfo[chatId]?.currentTextMessageId = -1
shareChatsInfo[chatId]?.updateTextMessageId = 1
shareChatsInfo[chatId]?.shouldSendViaBotMapMessage = true
shareChatsInfo[chatId]?.shouldSendViaBotTextMessage = true
}
}
fun save() {
val prefs = app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE)
val edit = prefs.edit()
val hiddenChatsSet = mutableSetOf<String>()
val hiddenChats = ArrayList(hiddenOnMapChats)
for (chatId in hiddenChats) {
hiddenChatsSet.add(chatId.toString())
}
edit.putStringSet(HIDDEN_ON_MAP_CHATS_KEY, hiddenChatsSet)
edit.putString(SHARING_MODE_KEY, currentSharingMode)
edit.putString(METRICS_CONSTANTS_KEY, metricsConstants.name)
edit.putString(SPEED_CONSTANTS_KEY, speedConstants.name)
edit.putLong(SEND_MY_LOC_INTERVAL_KEY, sendMyLocInterval)
edit.putLong(STALE_LOC_TIME_KEY, staleLocTime)
edit.putLong(LOC_HISTORY_TIME_KEY, locHistoryTime)
edit.putString(SHARE_TYPE_KEY, shareTypeValue)
edit.putString(APP_TO_CONNECT_PACKAGE_KEY, appToConnectPackage)
edit.putString(LIVE_NOW_SORT_TYPE_KEY, liveNowSortType.name)
edit.putBoolean(BATTERY_OPTIMISATION_ASKED, batteryOptimisationAsked)
edit.putBoolean(MONITORING_ENABLED, monitoringEnabled)
edit.putBoolean(SHOW_GPS_POINTS, showGpsPoints)
val jArray = convertShareChatsInfoToJson()
if (jArray != null) {
edit.putString(SHARE_CHATS_INFO_KEY, jArray.toString())
}
val jsonObject = convertShareDevicesToJson()
if (jsonObject != null) {
edit.putString(SHARE_DEVICES_KEY, jsonObject.toString())
}
edit.apply()
}
fun read() {
val prefs = app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE)
val hiddenChats = mutableSetOf<Long>()
val hiddenChatsSet = prefs.getStringSet(HIDDEN_ON_MAP_CHATS_KEY, mutableSetOf())
for (chatId in hiddenChatsSet) {
hiddenChats.add(chatId.toLong())
}
hiddenOnMapChats = hiddenChats
metricsConstants = MetricsConstants.valueOf(
prefs.getString(METRICS_CONSTANTS_KEY, MetricsConstants.KILOMETERS_AND_METERS.name)
)
speedConstants = SpeedConstants.valueOf(
prefs.getString(SPEED_CONSTANTS_KEY, SpeedConstants.KILOMETERS_PER_HOUR.name)
)
try {
parseShareChatsInfo(JSONArray(prefs.getString(SHARE_CHATS_INFO_KEY, "")))
} catch (e: JSONException) {
e.printStackTrace()
}
parseShareDevices(prefs.getString(SHARE_DEVICES_KEY, ""))
val sendMyLocDef = SEND_MY_LOC_VALUES_SEC[SEND_MY_LOC_DEFAULT_INDEX]
sendMyLocInterval = prefs.getLong(SEND_MY_LOC_INTERVAL_KEY, sendMyLocDef)
val staleLocDef = STALE_LOC_VALUES_SEC[STALE_LOC_DEFAULT_INDEX]
staleLocTime = prefs.getLong(STALE_LOC_TIME_KEY, staleLocDef)
val locHistoryDef = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX]
locHistoryTime = prefs.getLong(LOC_HISTORY_TIME_KEY, locHistoryDef)
val shareTypeDef = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX]
shareTypeValue = prefs.getString(SHARE_TYPE_KEY, shareTypeDef)
val currentUserId = app.telegramHelper.getCurrentUserId()
currentSharingMode = prefs.getString(SHARING_MODE_KEY, if (currentUserId != -1) currentUserId.toString() else "")
val defPackage = if (AppConnect.getInstalledApps(app).size == 1) AppConnect.getInstalledApps(app).first().appPackage else ""
appToConnectPackage = prefs.getString(APP_TO_CONNECT_PACKAGE_KEY, defPackage)
liveNowSortType = LiveNowSortType.valueOf(
prefs.getString(LIVE_NOW_SORT_TYPE_KEY, LiveNowSortType.SORT_BY_DISTANCE.name)
)
batteryOptimisationAsked = prefs.getBoolean(BATTERY_OPTIMISATION_ASKED,false)
monitoringEnabled = prefs.getBoolean(MONITORING_ENABLED,false)
showGpsPoints = prefs.getBoolean(SHOW_GPS_POINTS,false)
}
private fun convertShareDevicesToJson():JSONObject?{
return try {
val jsonObject = JSONObject()
val jArray = JSONArray()
shareDevices.forEach { device ->
val obj = JSONObject()
obj.put(DeviceBot.DEVICE_ID, device.id)
obj.put(DeviceBot.USER_ID, device.userId)
obj.put(DeviceBot.CHAT_ID, device.chatId)
obj.put(DeviceBot.DEVICE_NAME, device.deviceName)
obj.put(DeviceBot.EXTERNAL_ID, device.externalId)
obj.put(DeviceBot.DATA, JSONObject(device.data))
jArray.put(obj)
}
jsonObject.put(SHARE_DEVICES_KEY, jArray)
} catch (e: JSONException) {
e.printStackTrace()
null
}
}
private fun convertShareChatsInfoToJson(): JSONArray? {
return try {
val jArray = JSONArray()
shareChatsInfo.forEach { (chatId, chatInfo) ->
val obj = JSONObject()
obj.put(ShareChatInfo.CHAT_ID_KEY, chatId)
obj.put(ShareChatInfo.USER_ID_KEY, chatInfo.userId)
obj.put(ShareChatInfo.START_KEY, chatInfo.start)
obj.put(ShareChatInfo.LIVE_PERIOD_KEY, chatInfo.livePeriod)
obj.put(ShareChatInfo.LIMIT_KEY, chatInfo.currentMessageLimit)
obj.put(ShareChatInfo.UPDATE_TEXT_MESSAGE_ID_KEY, chatInfo.updateTextMessageId)
obj.put(ShareChatInfo.CURRENT_MAP_MESSAGE_ID_KEY, chatInfo.currentMapMessageId)
obj.put(ShareChatInfo.CURRENT_TEXT_MESSAGE_ID_KEY, chatInfo.currentTextMessageId)
obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY, chatInfo.userSetLivePeriod)
obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY, chatInfo.userSetLivePeriodStart)
obj.put(ShareChatInfo.LAST_TEXT_SUCCESSFUL_SEND_TIME_KEY, chatInfo.lastTextSuccessfulSendTime)
obj.put(ShareChatInfo.LAST_MAP_SUCCESSFUL_SEND_TIME_KEY, chatInfo.lastMapSuccessfulSendTime)
obj.put(ShareChatInfo.LAST_SEND_MAP_TIME_KEY, chatInfo.lastSendMapMessageTime)
obj.put(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY, chatInfo.lastSendTextMessageTime)
obj.put(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY, chatInfo.pendingTextMessage)
obj.put(ShareChatInfo.PENDING_MAP_MESSAGE_KEY, chatInfo.pendingMapMessage)
obj.put(ShareChatInfo.SENT_MESSAGES_KEY, chatInfo.sentMessages)
jArray.put(obj)
}
jArray
} catch (e: JSONException) {
e.printStackTrace()
null
}
}
private fun parseShareChatsInfo(json: JSONArray) {
for (i in 0 until json.length()) {
val obj = json.getJSONObject(i)
val shareInfo = ShareChatInfo().apply {
chatId = obj.optLong(ShareChatInfo.CHAT_ID_KEY)
userId = obj.optInt(ShareChatInfo.USER_ID_KEY)
start = obj.optLong(ShareChatInfo.START_KEY)
livePeriod = obj.optLong(ShareChatInfo.LIVE_PERIOD_KEY)
currentMessageLimit = obj.optLong(ShareChatInfo.LIMIT_KEY)
updateTextMessageId = obj.optInt(ShareChatInfo.UPDATE_TEXT_MESSAGE_ID_KEY)
currentMapMessageId = obj.optLong(ShareChatInfo.CURRENT_MAP_MESSAGE_ID_KEY)
currentTextMessageId = obj.optLong(ShareChatInfo.CURRENT_TEXT_MESSAGE_ID_KEY)
userSetLivePeriod = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY)
userSetLivePeriodStart = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY)
lastTextSuccessfulSendTime = obj.optLong(ShareChatInfo.LAST_TEXT_SUCCESSFUL_SEND_TIME_KEY)
lastMapSuccessfulSendTime = obj.optLong(ShareChatInfo.LAST_MAP_SUCCESSFUL_SEND_TIME_KEY)
lastSendMapMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_MAP_TIME_KEY)
lastSendTextMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY)
pendingTextMessage = obj.optBoolean(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY)
pendingMapMessage = obj.optBoolean(ShareChatInfo.PENDING_MAP_MESSAGE_KEY)
sentMessages = obj.optInt(ShareChatInfo.SENT_MESSAGES_KEY)
}
shareChatsInfo[shareInfo.chatId] = shareInfo
}
}
private fun parseShareDevices(json: String) {
shareDevices = OsmandApiUtils.parseJsonContents(json).toHashSet()
}
private fun getLiveNowChats() = app.telegramHelper.getMessagesByChatIds(locHistoryTime).keys
private fun updatePrefs() {
val prefs = app.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE)
val idsInUse = prefs.getBoolean(TITLES_REPLACED_WITH_IDS, false)
if (!idsInUse) {
val edit = prefs.edit()
edit.putStringSet(SHARE_LOCATION_CHATS_KEY, emptySet())
edit.putBoolean(TITLES_REPLACED_WITH_IDS, true)
edit.apply()
}
}
inner class SendMyLocPref : DurationPref(
R.drawable.ic_action_share_location,
R.string.send_my_location,
R.string.send_my_location_desc,
SEND_MY_LOC_VALUES_SEC
) {
override fun getCurrentValue() =
OsmandFormatter.getFormattedDuration(app, sendMyLocInterval)
override fun setCurrentValue(index: Int) {
sendMyLocInterval = values[index]
app.updateSendLocationInterval()
}
}
inner class StaleLocPref : DurationPref(
R.drawable.ic_action_time_span,
R.string.stale_location,
R.string.stale_location_desc,
STALE_LOC_VALUES_SEC
) {
override fun getCurrentValue() =
OsmandFormatter.getFormattedDuration(app, staleLocTime)
override fun setCurrentValue(index: Int) {
staleLocTime = values[index]
}
}
inner class LocHistoryPref : DurationPref(
R.drawable.ic_action_location_history,
R.string.location_history,
R.string.location_history_desc,
LOC_HISTORY_VALUES_SEC
) {
override fun getCurrentValue() =
OsmandFormatter.getFormattedDuration(app, locHistoryTime)
override fun setCurrentValue(index: Int) {
val value = values[index]
locHistoryTime = value
app.telegramHelper.messageActiveTimeSec = value
}
}
inner class ShareTypePref : DurationPref(
R.drawable.ic_action_location_history,
R.string.send_location_as,
R.string.send_location_as_descr,
emptyList()
) {
override fun getCurrentValue(): String {
return getTextValue(shareTypeValue)
}
override fun setCurrentValue(index: Int) {
val newSharingType = SHARE_TYPE_VALUES[index]
if (shareTypeValue != newSharingType && app.telegramHelper.getCurrentUser()?.id.toString() != currentSharingMode) {
shareChatsInfo.forEach { (_, shareInfo) ->
shareInfo.shouldSendViaBotTextMessage = true
shareInfo.shouldSendViaBotMapMessage = true
}
}
shareTypeValue = newSharingType
}
override fun getMenuItems(): List<String> {
return SHARE_TYPE_VALUES.map { getTextValue(it) }
}
private fun getTextValue(shareType: String): String {
return when (shareType) {
SHARE_TYPE_MAP -> app.getString(R.string.shared_string_map)
SHARE_TYPE_TEXT -> app.getString(R.string.shared_string_text)
SHARE_TYPE_MAP_AND_TEXT -> app.getString(R.string.map_and_text)
else -> ""
}
}
}
abstract inner class DurationPref(
@DrawableRes val iconId: Int,
@StringRes val titleId: Int,
@StringRes val descriptionId: Int,
val values: List<Long>
) {
abstract fun getCurrentValue(): String
abstract fun setCurrentValue(index: Int)
open fun getMenuItems() = values.map { OsmandFormatter.getFormattedDuration(app, it) }
}
enum class AppConnect(
@DrawableRes val iconId: Int,
@DrawableRes val whiteIconId: Int,
val title: String,
val appPackage: String,
val showOnlyInstalled: Boolean
) {
OSMAND_PLUS(
R.drawable.ic_logo_osmand_plus,
R.drawable.ic_action_osmand_plus,
"OsmAnd+",
OsmandAidlHelper.OSMAND_PLUS_PACKAGE_NAME,
false
),
OSMAND_FREE(
R.drawable.ic_logo_osmand_free,
R.drawable.ic_action_osmand_free,
"OsmAnd",
OsmandAidlHelper.OSMAND_FREE_PACKAGE_NAME,
false
),
OSMAND_NIGHTLY(
R.drawable.ic_logo_osmand_nightly,
R.drawable.ic_action_osmand_free,
"OsmAnd Nightly",
OsmandAidlHelper.OSMAND_NIGHTLY_PACKAGE_NAME,
true
);
companion object {
@DrawableRes
fun getWhiteIconId(appPackage: String): Int {
for (item in values()) {
if (item.appPackage == appPackage) {
return item.whiteIconId
}
}
return 0
}
@DrawableRes
fun getIconId(appPackage: String): Int {
for (item in values()) {
if (item.appPackage == appPackage) {
return item.iconId
}
}
return 0
}
fun getInstalledApps(context: Context) =
values().filter { AndroidUtils.isAppInstalled(context, it.appPackage) }
}
}
enum class LiveNowSortType(
@DrawableRes val iconId: Int,
@StringRes val titleId: Int,
@StringRes val shortTitleId: Int
) {
SORT_BY_GROUP(
R.drawable.ic_action_sort_by_group,
R.string.shared_string_group,
R.string.by_group
),
SORT_BY_NAME(
R.drawable.ic_action_sort_by_name,
R.string.shared_string_name,
R.string.by_name
),
SORT_BY_DISTANCE(
R.drawable.ic_action_sort_by_distance,
R.string.shared_string_distance,
R.string.by_distance
);
fun isSortByGroup() = this == SORT_BY_GROUP
}
enum class SharingStatusType(
@DrawableRes val iconId: Int,
@ColorRes val iconColorRes: Int,
val canResendLocation: Boolean
) {
NO_INTERNET(
R.drawable.ic_action_wifi_off,
R.color.sharing_status_icon_error,
true
),
SENDING(
R.drawable.ic_action_share_location,
R.color.sharing_status_icon_success,
false
),
NOT_POSSIBLE_TO_SENT_TO_CHATS(
R.drawable.ic_action_message_send_error,
R.color.sharing_status_icon_error,
true
),
NO_GPS(
R.drawable.ic_action_location_off,
R.color.sharing_status_icon_error,
false
),
INITIALIZING(
R.drawable.ic_action_connect,
R.color.sharing_status_icon_error,
false
);
}
class DeviceBot {
var id: Long = -1
var userId: Long = -1
var chatId: Long = -1
var deviceName: String = ""
var externalId: String = ""
var data: String = ""
companion object {
internal const val DEVICE_ID = "id"
internal const val USER_ID = "userId"
internal const val CHAT_ID = "chatId"
internal const val DEVICE_NAME = "deviceName"
internal const val EXTERNAL_ID = "externalId"
internal const val DATA = "data"
}
}
class SharingStatus {
var title: String = ""
var description: String = ""
var locationTime: Long = -1
var statusChangeTime: Long = -1
var chatsIds: MutableList<Long> = mutableListOf()
lateinit var statusType: SharingStatusType
fun getTitle(app: TelegramApplication): CharSequence {
return if (statusType != SharingStatusType.NOT_POSSIBLE_TO_SENT_TO_CHATS || chatsIds.isEmpty()) {
title
} else {
val spannableString = SpannableStringBuilder(title)
val iterator = chatsIds.iterator()
while (iterator.hasNext()) {
val chatId = iterator.next()
val chatTitle = app.telegramHelper.getChat(chatId)?.title
if (chatTitle != null) {
val start = spannableString.length
val newSpannable = if (iterator.hasNext()) " @$chatTitle," else " @$chatTitle."
spannableString.append(newSpannable)
spannableString.setSpan(ForegroundColorSpan(app.uiUtils.getActiveColor()), start, spannableString.length - 1, 0)
}
}
spannableString
}
}
}
class ShareChatInfo {
var chatId = -1L
var userId = -1
var start = -1L
var livePeriod = -1L
var updateTextMessageId = 1
var currentMessageLimit = -1L
var currentMapMessageId = -1L
var oldMapMessageId = -1L
var currentTextMessageId = -1L
var oldTextMessageId = -1L
var userSetLivePeriod = -1L
var userSetLivePeriodStart = -1L
var lastTextSuccessfulSendTime = -1L
var lastMapSuccessfulSendTime = -1L
var lastSendTextMessageTime = -1
var lastSendMapMessageTime = -1
var sentMessages = 0
var pendingTdLibText = 0
var pendingTdLibMap = 0
var pendingTextMessage = false
var pendingMapMessage = false
var shouldSendViaBotTextMessage = false
var shouldSendViaBotMapMessage = false
var hasSharingError = false
var additionalActiveTime = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]
fun getNextAdditionalActiveTime(): Long {
var index = ADDITIONAL_ACTIVE_TIME_VALUES_SEC.indexOf(additionalActiveTime)
return if (ADDITIONAL_ACTIVE_TIME_VALUES_SEC.lastIndex > index) {
ADDITIONAL_ACTIVE_TIME_VALUES_SEC[++index]
} else {
ADDITIONAL_ACTIVE_TIME_VALUES_SEC[index]
}
}
fun getChatLiveMessageExpireTime(): Long {
return userSetLivePeriod - ((System.currentTimeMillis() / 1000) - start)
}
companion object {
internal const val CHAT_ID_KEY = "chatId"
internal const val USER_ID_KEY = "userId"
internal const val START_KEY = "start"
internal const val LIVE_PERIOD_KEY = "livePeriod"
internal const val LIMIT_KEY = "limit"
internal const val UPDATE_TEXT_MESSAGE_ID_KEY = "updateTextMessageId"
internal const val CURRENT_MAP_MESSAGE_ID_KEY = "currentMapMessageId"
internal const val CURRENT_TEXT_MESSAGE_ID_KEY = "currentTextMessageId"
internal const val USER_SET_LIVE_PERIOD_KEY = "userSetLivePeriod"
internal const val USER_SET_LIVE_PERIOD_START_KEY = "userSetLivePeriodStart"
internal const val LAST_MAP_SUCCESSFUL_SEND_TIME_KEY = "lastMapSuccessfulSendTime"
internal const val LAST_TEXT_SUCCESSFUL_SEND_TIME_KEY = "lastTextSuccessfulSendTime"
internal const val LAST_SEND_MAP_TIME_KEY = "lastSendMapMessageTime"
internal const val LAST_SEND_TEXT_TIME_KEY = "lastSendTextMessageTime"
internal const val PENDING_TEXT_MESSAGE_KEY = "pendingTextMessage"
internal const val PENDING_MAP_MESSAGE_KEY = "pendingMapMessage"
internal const val SENT_MESSAGES_KEY = "sentMessages"
}
}
}