NekoX/TMessagesProj/src/main/java/org/telegram/messenger/LocationController.java

659 lines
29 KiB
Java

/*
* This is the source code of Telegram for Android v. 5.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2018.
*/
package org.telegram.messenger;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.LongSparseArray;
import android.util.SparseIntArray;
import org.telegram.SQLite.SQLiteCursor;
import org.telegram.SQLite.SQLitePreparedStatement;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.NativeByteBuffer;
import org.telegram.tgnet.TLRPC;
import java.util.ArrayList;
public class LocationController implements NotificationCenter.NotificationCenterDelegate {
private LongSparseArray<SharingLocationInfo> sharingLocationsMap = new LongSparseArray<>();
private ArrayList<SharingLocationInfo> sharingLocations = new ArrayList<>();
public LongSparseArray<ArrayList<TLRPC.Message>> locationsCache = new LongSparseArray<>();
private LocationManager locationManager;
private GpsLocationListener gpsLocationListener = new GpsLocationListener();
private GpsLocationListener networkLocationListener = new GpsLocationListener();
private GpsLocationListener passiveLocationListener = new GpsLocationListener();
private Location lastKnownLocation;
private long lastLocationSendTime;
private boolean locationSentSinceLastGoogleMapUpdate = true;
private long lastLocationStartTime;
private boolean started;
private boolean lastLocationByGoogleMaps;
private SparseIntArray requests = new SparseIntArray();
private LongSparseArray<Boolean> cacheRequests = new LongSparseArray<>();
public ArrayList<SharingLocationInfo> sharingLocationsUI = new ArrayList<>();
private LongSparseArray<SharingLocationInfo> sharingLocationsMapUI = new LongSparseArray<>();
private final static int BACKGROUD_UPDATE_TIME = 90 * 1000;
private final static int LOCATION_ACQUIRE_TIME = 10 * 1000;
private final static int FOREGROUND_UPDATE_TIME = 20 * 1000;
private final static double eps = 0.0001;
private int currentAccount;
private static volatile LocationController Instance[] = new LocationController[UserConfig.MAX_ACCOUNT_COUNT];
public static LocationController getInstance(int num) {
LocationController localInstance = Instance[num];
if (localInstance == null) {
synchronized (LocationController.class) {
localInstance = Instance[num];
if (localInstance == null) {
Instance[num] = localInstance = new LocationController(num);
}
}
}
return localInstance;
}
public static class SharingLocationInfo {
public long did;
public int mid;
public int stopTime;
public int period;
public MessageObject messageObject;
}
private class GpsLocationListener implements LocationListener {
@Override
public void onLocationChanged(Location location) {
if (location == null) {
return;
}
if (lastKnownLocation != null && (this == networkLocationListener || this == passiveLocationListener)) {
if (!started && location.distanceTo(lastKnownLocation) > 20) {
lastKnownLocation = location;
lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + 5000;
}
} else {
lastKnownLocation = location;
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
public LocationController(final int instance) {
currentAccount = instance;
locationManager = (LocationManager) ApplicationLoader.applicationContext.getSystemService(Context.LOCATION_SERVICE);
AndroidUtilities.runOnUIThread(() -> {
LocationController locationController = getInstance(currentAccount);
NotificationCenter.getInstance(currentAccount).addObserver(locationController, NotificationCenter.didReceiveNewMessages);
NotificationCenter.getInstance(currentAccount).addObserver(locationController, NotificationCenter.messagesDeleted);
NotificationCenter.getInstance(currentAccount).addObserver(locationController, NotificationCenter.replaceMessagesObjects);
});
loadSharingLocations();
}
@SuppressWarnings("unchecked")
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (id == NotificationCenter.didReceiveNewMessages) {
long did = (Long) args[0];
if (!isSharingLocation(did)) {
return;
}
ArrayList<TLRPC.Message> messages = locationsCache.get(did);
if (messages == null) {
return;
}
ArrayList<MessageObject> arr = (ArrayList<MessageObject>) args[1];
boolean added = false;
for (int a = 0; a < arr.size(); a++) {
MessageObject messageObject = arr.get(a);
if (messageObject.isLiveLocation()) {
added = true;
boolean replaced = false;
for (int b = 0; b < messages.size(); b++) {
if (messages.get(b).from_id == messageObject.messageOwner.from_id) {
replaced = true;
messages.set(b, messageObject.messageOwner);
break;
}
}
if (!replaced) {
messages.add(messageObject.messageOwner);
}
}
}
if (added) {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, did, currentAccount);
}
} else if (id == NotificationCenter.messagesDeleted) {
if (!sharingLocationsUI.isEmpty()) {
ArrayList<Integer> markAsDeletedMessages = (ArrayList<Integer>) args[0];
int channelId = (Integer) args[1];
ArrayList<Long> toRemove = null;
for (int a = 0; a < sharingLocationsUI.size(); a++) {
SharingLocationInfo info = sharingLocationsUI.get(a);
int messageChannelId = info.messageObject != null ? info.messageObject.getChannelId() : 0;
if (channelId != messageChannelId) {
continue;
}
if (markAsDeletedMessages.contains(info.mid)) {
if (toRemove == null) {
toRemove = new ArrayList<>();
}
toRemove.add(info.did);
}
}
if (toRemove != null) {
for (int a = 0; a < toRemove.size(); a++) {
removeSharingLocation(toRemove.get(a));
}
}
}
} else if (id == NotificationCenter.replaceMessagesObjects) {
long did = (long) args[0];
if (!isSharingLocation(did)) {
return;
}
ArrayList<TLRPC.Message> messages = locationsCache.get(did);
if (messages == null) {
return;
}
boolean updated = false;
ArrayList<MessageObject> messageObjects = (ArrayList<MessageObject>) args[1];
for (int a = 0; a < messageObjects.size(); a++) {
MessageObject messageObject = messageObjects.get(a);
for (int b = 0; b < messages.size(); b++) {
if (messages.get(b).from_id == messageObject.messageOwner.from_id) {
if (!messageObject.isLiveLocation()) {
messages.remove(b);
} else {
messages.set(b, messageObject.messageOwner);
}
updated = true;
break;
}
}
}
if (updated) {
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, did, currentAccount);
}
}
}
private void broadcastLastKnownLocation() {
if (lastKnownLocation == null) {
return;
}
if (requests.size() != 0) {
for (int a = 0; a < requests.size(); a++) {
ConnectionsManager.getInstance(currentAccount).cancelRequest(requests.keyAt(a), false);
}
requests.clear();
}
int date = ConnectionsManager.getInstance(currentAccount).getCurrentTime();
for (int a = 0; a < sharingLocations.size(); a++) {
final SharingLocationInfo info = sharingLocations.get(a);
if (info.messageObject.messageOwner.media != null && info.messageObject.messageOwner.media.geo != null) {
int messageDate = info.messageObject.messageOwner.edit_date != 0 ? info.messageObject.messageOwner.edit_date : info.messageObject.messageOwner.date;
TLRPC.GeoPoint point = info.messageObject.messageOwner.media.geo;
if (Math.abs(date - messageDate) < 30 && Math.abs(point.lat - lastKnownLocation.getLatitude()) <= eps && Math.abs(point._long - lastKnownLocation.getLongitude()) <= eps) {
continue;
}
}
TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage();
req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) info.did);
req.id = info.mid;
req.flags |= 16384;
req.media = new TLRPC.TL_inputMediaGeoLive();
req.media.stopped = false;
req.media.geo_point = new TLRPC.TL_inputGeoPoint();
req.media.geo_point.lat = AndroidUtilities.fixLocationCoord(lastKnownLocation.getLatitude());
req.media.geo_point._long = AndroidUtilities.fixLocationCoord(lastKnownLocation.getLongitude());
final int[] reqId = new int[1];
reqId[0] = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
if (error != null) {
if (error.text.equals("MESSAGE_ID_INVALID")) {
sharingLocations.remove(info);
sharingLocationsMap.remove(info.did);
saveSharingLocation(info, 1);
requests.delete(reqId[0]);
AndroidUtilities.runOnUIThread(() -> {
sharingLocationsUI.remove(info);
sharingLocationsMapUI.remove(info.did);
if (sharingLocationsUI.isEmpty()) {
stopService();
}
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsChanged);
});
}
return;
}
TLRPC.Updates updates = (TLRPC.Updates) response;
boolean updated = false;
for (int a1 = 0; a1 < updates.updates.size(); a1++) {
TLRPC.Update update = updates.updates.get(a1);
if (update instanceof TLRPC.TL_updateEditMessage) {
updated = true;
info.messageObject.messageOwner = ((TLRPC.TL_updateEditMessage) update).message;
} else if (update instanceof TLRPC.TL_updateEditChannelMessage) {
updated = true;
info.messageObject.messageOwner = ((TLRPC.TL_updateEditChannelMessage) update).message;
}
}
if (updated) {
saveSharingLocation(info, 0);
}
MessagesController.getInstance(currentAccount).processUpdates(updates, false);
});
requests.put(reqId[0], 0);
}
ConnectionsManager.getInstance(currentAccount).resumeNetworkMaybe();
stop(false);
}
protected void update() {
if (sharingLocations.isEmpty()) {
return;
}
for (int a = 0; a < sharingLocations.size(); a++) {
final SharingLocationInfo info = sharingLocations.get(a);
int currentTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime();
if (info.stopTime <= currentTime) {
sharingLocations.remove(a);
sharingLocationsMap.remove(info.did);
saveSharingLocation(info, 1);
AndroidUtilities.runOnUIThread(() -> {
sharingLocationsUI.remove(info);
sharingLocationsMapUI.remove(info.did);
if (sharingLocationsUI.isEmpty()) {
stopService();
}
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsChanged);
});
a--;
}
}
if (!started) {
if (Math.abs(lastLocationSendTime - System.currentTimeMillis()) > BACKGROUD_UPDATE_TIME) {
lastLocationStartTime = System.currentTimeMillis();
start();
}
} else {
if (lastLocationByGoogleMaps || Math.abs(lastLocationStartTime - System.currentTimeMillis()) > LOCATION_ACQUIRE_TIME) {
lastLocationByGoogleMaps = false;
locationSentSinceLastGoogleMapUpdate = true;
lastLocationSendTime = System.currentTimeMillis();
broadcastLastKnownLocation();
}
}
}
public void cleanup() {
sharingLocationsUI.clear();
sharingLocationsMapUI.clear();
locationsCache.clear();
cacheRequests.clear();
stopService();
Utilities.stageQueue.postRunnable(() -> {
requests.clear();
sharingLocationsMap.clear();
sharingLocations.clear();
lastKnownLocation = null;
stop(true);
});
}
protected void addSharingLocation(long did, int mid, int period, TLRPC.Message message) {
final SharingLocationInfo info = new SharingLocationInfo();
info.did = did;
info.mid = mid;
info.period = period;
info.messageObject = new MessageObject(currentAccount, message, false);
info.stopTime = ConnectionsManager.getInstance(currentAccount).getCurrentTime() + period;
final SharingLocationInfo old = sharingLocationsMap.get(did);
sharingLocationsMap.put(did, info);
if (old != null) {
sharingLocations.remove(old);
}
sharingLocations.add(info);
saveSharingLocation(info, 0);
lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + 5000;
AndroidUtilities.runOnUIThread(() -> {
if (old != null) {
sharingLocationsUI.remove(old);
}
sharingLocationsUI.add(info);
sharingLocationsMapUI.put(info.did, info);
startService();
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsChanged);
});
}
public boolean isSharingLocation(long did) {
return sharingLocationsMapUI.indexOfKey(did) >= 0;
}
public SharingLocationInfo getSharingLocationInfo(long did) {
return sharingLocationsMapUI.get(did);
}
private void loadSharingLocations() {
MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> {
final ArrayList<SharingLocationInfo> result = new ArrayList<>();
final ArrayList<TLRPC.User> users = new ArrayList<>();
final ArrayList<TLRPC.Chat> chats = new ArrayList<>();
try {
ArrayList<Integer> usersToLoad = new ArrayList<>();
ArrayList<Integer> chatsToLoad = new ArrayList<>();
SQLiteCursor cursor = MessagesStorage.getInstance(currentAccount).getDatabase().queryFinalized("SELECT uid, mid, date, period, message FROM sharing_locations WHERE 1");
while (cursor.next()) {
SharingLocationInfo info = new SharingLocationInfo();
info.did = cursor.longValue(0);
info.mid = cursor.intValue(1);
info.stopTime = cursor.intValue(2);
info.period = cursor.intValue(3);
NativeByteBuffer data = cursor.byteBufferValue(4);
if (data != null) {
info.messageObject = new MessageObject(currentAccount, TLRPC.Message.TLdeserialize(data, data.readInt32(false), false), false);
MessagesStorage.addUsersAndChatsFromMessage(info.messageObject.messageOwner, usersToLoad, chatsToLoad);
data.reuse();
}
result.add(info);
int lower_id = (int) info.did;
int high_id = (int) (info.did >> 32);
if (lower_id != 0) {
if (lower_id < 0) {
if (!chatsToLoad.contains(-lower_id)) {
chatsToLoad.add(-lower_id);
}
} else {
if (!usersToLoad.contains(lower_id)) {
usersToLoad.add(lower_id);
}
}
} else {
/*if (!encryptedChatIds.contains(high_id)) {
encryptedChatIds.add(high_id);
}*/
}
}
cursor.dispose();
if (!chatsToLoad.isEmpty()) {
MessagesStorage.getInstance(currentAccount).getChatsInternal(TextUtils.join(",", chatsToLoad), chats);
}
if (!usersToLoad.isEmpty()) {
MessagesStorage.getInstance(currentAccount).getUsersInternal(TextUtils.join(",", usersToLoad), users);
}
} catch (Exception e) {
FileLog.e(e);
}
if (!result.isEmpty()) {
AndroidUtilities.runOnUIThread(() -> {
MessagesController.getInstance(currentAccount).putUsers(users, true);
MessagesController.getInstance(currentAccount).putChats(chats, true);
Utilities.stageQueue.postRunnable(() -> {
sharingLocations.addAll(result);
for (int a = 0; a < sharingLocations.size(); a++) {
SharingLocationInfo info = sharingLocations.get(a);
sharingLocationsMap.put(info.did, info);
}
AndroidUtilities.runOnUIThread(() -> {
sharingLocationsUI.addAll(result);
for (int a = 0; a < result.size(); a++) {
SharingLocationInfo info = result.get(a);
sharingLocationsMapUI.put(info.did, info);
}
startService();
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsChanged);
});
});
});
}
});
}
private void saveSharingLocation(final SharingLocationInfo info, final int remove) {
MessagesStorage.getInstance(currentAccount).getStorageQueue().postRunnable(() -> {
try {
if (remove == 2) {
MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM sharing_locations WHERE 1").stepThis().dispose();
} else if (remove == 1) {
if (info == null) {
return;
}
MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("DELETE FROM sharing_locations WHERE uid = " + info.did).stepThis().dispose();
} else {
if (info == null) {
return;
}
SQLitePreparedStatement state = MessagesStorage.getInstance(currentAccount).getDatabase().executeFast("REPLACE INTO sharing_locations VALUES(?, ?, ?, ?, ?)");
state.requery();
NativeByteBuffer data = new NativeByteBuffer(info.messageObject.messageOwner.getObjectSize());
info.messageObject.messageOwner.serializeToStream(data);
state.bindLong(1, info.did);
state.bindInteger(2, info.mid);
state.bindInteger(3, info.stopTime);
state.bindInteger(4, info.period);
state.bindByteBuffer(5, data);
state.step();
state.dispose();
data.reuse();
}
} catch (Exception e) {
FileLog.e(e);
}
});
}
public void removeSharingLocation(final long did) {
Utilities.stageQueue.postRunnable(() -> {
final SharingLocationInfo info = sharingLocationsMap.get(did);
sharingLocationsMap.remove(did);
if (info != null) {
TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage();
req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) info.did);
req.id = info.mid;
req.flags |= 16384;
req.media = new TLRPC.TL_inputMediaGeoLive();
req.media.stopped = true;
req.media.geo_point = new TLRPC.TL_inputGeoPointEmpty();
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
if (error != null) {
return;
}
MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false);
});
sharingLocations.remove(info);
saveSharingLocation(info, 1);
AndroidUtilities.runOnUIThread(() -> {
sharingLocationsUI.remove(info);
sharingLocationsMapUI.remove(info.did);
if (sharingLocationsUI.isEmpty()) {
stopService();
}
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsChanged);
});
if (sharingLocations.isEmpty()) {
stop(true);
}
}
});
}
private void startService() {
try {
/*if (Build.VERSION.SDK_INT >= 26) {
ApplicationLoader.applicationContext.startForegroundService(new Intent(ApplicationLoader.applicationContext, LocationSharingService.class));
} else {*/
ApplicationLoader.applicationContext.startService(new Intent(ApplicationLoader.applicationContext, LocationSharingService.class));
//}
} catch (Throwable e) {
FileLog.e(e);
}
}
private void stopService() {
ApplicationLoader.applicationContext.stopService(new Intent(ApplicationLoader.applicationContext, LocationSharingService.class));
}
public void removeAllLocationSharings() {
Utilities.stageQueue.postRunnable(() -> {
for (int a = 0; a < sharingLocations.size(); a++) {
SharingLocationInfo info = sharingLocations.get(a);
TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage();
req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) info.did);
req.id = info.mid;
req.flags |= 16384;
req.media = new TLRPC.TL_inputMediaGeoLive();
req.media.stopped = true;
req.media.geo_point = new TLRPC.TL_inputGeoPointEmpty();
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
if (error != null) {
return;
}
MessagesController.getInstance(currentAccount).processUpdates((TLRPC.Updates) response, false);
});
}
sharingLocations.clear();
sharingLocationsMap.clear();
saveSharingLocation(null, 2);
stop(true);
AndroidUtilities.runOnUIThread(() -> {
sharingLocationsUI.clear();
sharingLocationsMapUI.clear();
stopService();
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsChanged);
});
});
}
public void setGoogleMapLocation(Location location, boolean first) {
if (location == null) {
return;
}
lastLocationByGoogleMaps = true;
if (first || lastKnownLocation != null && lastKnownLocation.distanceTo(location) >= 20) {
lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME;
locationSentSinceLastGoogleMapUpdate = false;
} else if (locationSentSinceLastGoogleMapUpdate) {
lastLocationSendTime = System.currentTimeMillis() - BACKGROUD_UPDATE_TIME + FOREGROUND_UPDATE_TIME;
locationSentSinceLastGoogleMapUpdate = false;
}
lastKnownLocation = location;
}
private void start() {
if (started) {
return;
}
lastLocationStartTime = System.currentTimeMillis();
started = true;
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1, 0, gpsLocationListener);
} catch (Exception e) {
FileLog.e(e);
}
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1, 0, networkLocationListener);
} catch (Exception e) {
FileLog.e(e);
}
try {
locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 1, 0, passiveLocationListener);
} catch (Exception e) {
FileLog.e(e);
}
if (lastKnownLocation == null) {
try {
lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKnownLocation == null) {
lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
} catch (Exception e) {
FileLog.e(e);
}
}
}
private void stop(boolean empty) {
started = false;
locationManager.removeUpdates(gpsLocationListener);
if (empty) {
locationManager.removeUpdates(networkLocationListener);
locationManager.removeUpdates(passiveLocationListener);
}
}
public void loadLiveLocations(final long did) {
if (cacheRequests.indexOfKey(did) >= 0) {
return;
}
cacheRequests.put(did, true);
TLRPC.TL_messages_getRecentLocations req = new TLRPC.TL_messages_getRecentLocations();
req.peer = MessagesController.getInstance(currentAccount).getInputPeer((int) did);
req.limit = 100;
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> {
if (error != null) {
return;
}
AndroidUtilities.runOnUIThread(() -> {
cacheRequests.delete(did);
TLRPC.messages_Messages res = (TLRPC.messages_Messages) response;
for (int a = 0; a < res.messages.size(); a++) {
if (!(res.messages.get(a).media instanceof TLRPC.TL_messageMediaGeoLive)) {
res.messages.remove(a);
a--;
}
}
MessagesStorage.getInstance(currentAccount).putUsersAndChats(res.users, res.chats, true, true);
MessagesController.getInstance(currentAccount).putUsers(res.users, false);
MessagesController.getInstance(currentAccount).putChats(res.chats, false);
locationsCache.put(did, res.messages);
NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.liveLocationsCacheChanged, did, currentAccount);
});
});
}
public static int getLocationsCount() {
int count = 0;
for (int a = 0; a < UserConfig.MAX_ACCOUNT_COUNT; a++) {
count += LocationController.getInstance(a).sharingLocationsUI.size();
}
return count;
}
}