package net.osmand.plus.osmo;
import com.google.gson.Gson;
import net.osmand.Location;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.GpxSelectionHelper;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.plus.osmo.OsMoGroupsStorage.OsMoDevice;
import net.osmand.plus.osmo.OsMoGroupsStorage.OsMoGroup;
import net.osmand.plus.osmo.OsMoTracker.OsmoTrackerListener;
import net.osmand.util.Algorithms;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class OsMoGroups implements OsMoReactor, OsmoTrackerListener {
private static final String GROUP_NAME = "name";
private static final String ACTIVE = "active";
private static final String GROUP_ID = "group_id";
private static final String EXPIRE_TIME = "expireTime";
private static final String DESCRIPTION = "description";
private static final String POLICY = "policy";
private static final String USERS = "users";
private static final String USER_NAME = "name";
private static final String USER_COLOR = "color";
private static final String DELETED = "deleted";
private static final String GROUP_TRACKER_ID = "group_tracker_id";
private static final String LAST_ONLINE = "last_online";
private static final String TRACK = "track";
private static final String POINT = "point";
private OsMoTracker tracker;
private OsMoService service;
private OsMoGroupsStorage storage;
private ArrayList<OsMoGroupsUIListener> uiListeners = new ArrayList<>();
private OsMoPlugin plugin;
private OsmandApplication app;
private Gson gson = new Gson();
public interface OsMoGroupsUIListener {
public void groupsListChange(String operation, OsMoGroup group);
public void deviceLocationChanged(OsMoDevice device);
}
public OsMoGroups(OsMoPlugin plugin, OsMoService service, OsMoTracker tracker, OsmandApplication app) {
this.plugin = plugin;
this.service = service;
this.tracker = tracker;
this.app = app;
service.registerReactor(this);
tracker.setTrackerListener(this);
storage = new OsMoGroupsStorage(this, app.getSettings().OSMO_GROUPS);
if (!service.isLoggedIn()) {
storage.load();
}
}
public void addUiListeners(OsMoGroupsUIListener uiListener) {
if (!uiListeners.contains(uiListener)) {
uiListeners.add(uiListener);
}
}
public void removeUiListener(OsMoGroupsUIListener uiListener) {
uiListeners.remove(uiListener);
}
private void connectDeviceImpl(OsMoDevice d) {
d.enabled = true;
d.active = true;
String mid = service.getMyTrackerId();
String mgid = service.getMyGroupTrackerId();
if ((mid == null || !mid.equals(d.getTrackerId()))
&& (mgid == null || !mgid.equals(d.getTrackerId()))) {
tracker.startTrackingId(d);
}
}
@Override
public void onConnected() {
if (service.isLoggedIn()) {
service.pushCommand("GROUP_GET_ALL");
}
}
@Override
public void onDisconnected(String msg) {
}
private String connectGroupImpl(OsMoGroup g) {
g.enabled = true;
String operation = "GROUP_CONNECT:" + g.groupId;
service.pushCommand("GROUP_CONNECT:" + g.groupId);
return operation;
}
public String connectGroup(OsMoGroup model) {
String op = connectGroupImpl(model);
storage.save();
return op;
}
public String disconnectGroup(OsMoGroup model) {
model.enabled = false;
String operation = "GROUP_DISCONNECT:" + model.groupId;
service.pushCommand(operation);
for (OsMoDevice d : model.getGroupUsers(null)) {
tracker.stopTrackingId(d);
}
storage.save();
return operation;
}
private void disconnectImpl(OsMoDevice model) {
model.active = false;
tracker.stopTrackingId(model);
}
public Collection<OsMoGroup> getGroups() {
return storage.getGroups();
}
public void showErrorMessage(String message) {
service.showErrorMessage(message);
}
@Override
public void locationChange(String trackerId, Location location) {
for (OsMoGroup g : getGroups()) {
OsMoDevice d = g.updateLastLocation(trackerId, location);
if (d != null && uiListeners != null) {
for (OsMoGroupsUIListener listener : uiListeners) {
listener.deviceLocationChanged(d);
}
}
}
}
@Override
public boolean acceptCommand(String command, String gid, String data, JSONObject obj, OsMoThread thread) {
boolean processed = false;
String operation = command + ":" + gid;
OsMoGroup group = null;
if (command.equalsIgnoreCase("GP")) {
group = storage.getGroup(gid);
if (group != null && gid.length() > 0) {
List<OsMoDevice> delta = mergeGroup(group, obj, false, false);
String mygid = service.getMyGroupTrackerId();
StringBuilder b = new StringBuilder();
for (OsMoDevice d : delta) {
if (d.getDeletedTimestamp() != 0 && d.isEnabled()) {
if (group.name != null && !d.getTrackerId().equals(mygid)) {
b.append(app.getString(R.string.osmo_user_left, d.getVisibleName(), group.getVisibleName(app))).append("\n");
}
disconnectImpl(d);
} else if (!d.isActive()) {
if (group.name != null && !d.getTrackerId().equals(mygid)) {
b.append(app.getString(R.string.osmo_user_joined, d.getVisibleName(), group.getVisibleName(app))).append("\n");
}
connectDeviceImpl(d);
}
}
if (b.length() > 0 && app.getSettings().OSMO_SHOW_GROUP_NOTIFICATIONS.get()) {
app.showToastMessage(b.toString().trim());
}
storage.save();
}
processed = true;
} else if (command.equalsIgnoreCase("GROUP_DISCONNECT")) {
group = storage.getGroup(gid);
if (group != null) {
if (obj == null || !obj.has("error")) {
disconnectAllGroupUsers(group);
disableGroupTracks(group, group.groupTracks);
disableGroupTracks(group, Collections.singleton(group.name + " points.gpx"));
}
processed = true;
}
} else if (command.equalsIgnoreCase("GROUP_CONNECT")) {
group = storage.getGroup(gid);
if (group != null) {
mergeGroup(group, obj, true, true);
group.active = true;
// connect to enabled devices in group
for (OsMoDevice d : group.getGroupUsers(null)) {
if (d.isEnabled()) {
d.active = false;
connectDeviceImpl(d);
}
}
storage.save();
}
processed = true;
} else if (command.equalsIgnoreCase("GROUP_CREATE") || command.equalsIgnoreCase("GROUP_JOIN")) {
if (command.equalsIgnoreCase("GROUP_CREATE")) {
operation = command;
try {
gid = obj.getString(GROUP_ID);
} catch (JSONException e) {
e.printStackTrace();
service.showErrorMessage(e.getMessage());
return true;
}
}
group = storage.getGroup(gid);
if (group == null) {
group = new OsMoGroup();
group.groupId = gid;
storage.addGroup(group);
}
connectGroupImpl(group);
storage.save();
processed = true;
} else if (command.equals("GROUP_LEAVE")) {
group = storage.getGroup(gid);
if (group != null) {
storage.deleteGroup(group);
}
storage.save();
processed = true;
} else if (command.equals("GROUP_GET_ALL")) {
try {
JSONArray arr = new JSONArray(data);
int arrLength = arr.length();
if (arrLength > 0) {
Map<String, OsMoGroup> receivedGroups = new HashMap<String, OsMoGroupsStorage.OsMoGroup>();
for (int i = 0; i < arrLength; i++) {
JSONObject o = arr.getJSONObject(i);
OsMoGroup parsedGroup = parseGroup(o);
receivedGroups.put(parsedGroup.getGroupId(), parsedGroup);
}
storage.mergeGroups(receivedGroups);
storage.save();
}
processed = true;
for (OsMoGroup g : storage.getGroups()) {
if (!g.isMainGroup() && g.isEnabled()) {
connectGroupImpl(g);
}
}
} catch (JSONException e) {
e.printStackTrace();
service.showErrorMessage(e.getMessage());
return processed;
}
}
if (processed && uiListeners != null) {
for (OsMoGroupsUIListener listener : uiListeners) {
listener.groupsListChange(operation, group);
}
}
return processed;
}
private void disableGroupTracks(OsMoGroup group, Collection<String> tracks) {
if (!tracks.isEmpty()) {
GpxSelectionHelper helper = app.getSelectedGpxHelper();
for (String t : tracks) {
SelectedGpxFile sg = helper.getSelectedFileByName("osmo/" + t);
if (sg != null && sg.getGpxFile() != null) {
helper.selectGpxFile(sg.getGpxFile(), false, false);
}
}
plugin.refreshMap();
}
}
private OsMoGroup parseGroup(JSONObject obj) throws JSONException {
OsMoGroup groupe = new OsMoGroup();
groupe.enabled = !(obj.has(ACTIVE) && ("0".equals(obj.getString(ACTIVE)) ||
"false".equals(obj.getString(ACTIVE))));
groupe.name = obj.optString(GROUP_NAME);
groupe.groupId = obj.getString(GROUP_ID);
groupe.description = obj.optString(DESCRIPTION);
groupe.policy = obj.optString(POLICY);
return groupe;
}
private void parseGroup(JSONObject obj, OsMoGroup gr) {
try {
if (obj.has(GROUP_NAME)) {
gr.name = obj.getString(GROUP_NAME);
}
if (obj.has(DESCRIPTION)) {
gr.description = obj.getString(DESCRIPTION);
}
if (obj.has(POLICY)) {
gr.policy = obj.getString(POLICY);
}
if (obj.has(EXPIRE_TIME)) {
gr.expireTime = obj.getLong(EXPIRE_TIME);
}
} catch (JSONException e) {
e.printStackTrace();
service.showErrorMessage(e.getMessage());
}
}
private void disconnectAllGroupUsers(OsMoGroup gr) {
gr.active = false;
for (OsMoDevice d : gr.getGroupUsers(null)) {
disconnectImpl(d);
}
}
private List<OsMoDevice> mergeGroup(OsMoGroup gr, JSONObject obj, boolean deleteUsers,
boolean isGroupConnect) {
List<OsMoDevice> delta = new ArrayList<OsMoDevice>();
try {
if (obj.has("group")) {
parseGroup(obj.getJSONObject("group"), gr);
}
Map<String, OsMoDevice> toDelete = new HashMap<String, OsMoDevice>(gr.users);
if (obj.has(USERS)) {
JSONArray arr = obj.getJSONArray(USERS);
for (int i = 0; i < arr.length(); i++) {
JSONObject o = (JSONObject) arr.get(i);
String tid = o.getString(GROUP_TRACKER_ID);
OsMoDevice device = toDelete.remove(tid);
if (device == null) {
device = new OsMoDevice();
device.group = gr;
device.trackerId = tid;
device.enabled = true;
gr.users.put(tid, device);
}
if (o.has(USER_NAME)) {
device.serverName = o.getString(USER_NAME);
}
if (o.has(DELETED)) {
device.deleted = System.currentTimeMillis();
} else {
device.deleted = 0;
}
if (o.has(LAST_ONLINE)) {
device.lastOnline = o.getLong(LAST_ONLINE);
}
if (o.has(USER_COLOR)) {
device.serverColor = net.osmand.util.Algorithms.parseColor(o.getString(USER_COLOR));
}
delta.add(device);
}
}
if (obj.has(TRACK)) {
JSONArray ar = obj.getJSONArray(TRACK);
List<JSONObject> a = new ArrayList<>(ar.length());
Set<String> toDeleteT = new HashSet<String>(gr.groupTracks);
gr.groupTracks.clear();
for (int i = 0; i < ar.length(); i++) {
JSONObject trackJson = (JSONObject) ar.get(i);
if (!trackJson.has(DELETED)) {
String track = trackJson.getString("name") + ".gpx";
gr.groupTracks.add(track);
toDeleteT.remove(track);
a.add(trackJson);
}
}
plugin.getDownloadGpxTask(true).execute(a.toArray(new JSONObject[a.size()]));
disableGroupTracks(gr, toDeleteT);
}
if (obj.has(POINT)) {
JSONArray ar = obj.getJSONArray(POINT);
ArrayList<WptPt> points = new ArrayList<WptPt>();
long modify = obj.has("point_modify") ? obj.getLong("point_modify") : System.currentTimeMillis();
JSONObject[] a = new JSONObject[ar.length()];
for (int i = 0; i < a.length; i++) {
a[i] = (JSONObject) ar.get(i);
final JSONObject jobj = a[i];
WptPt pt = new WptPt();
if (jobj.has("deleted")) {
pt.deleted = true;
} else {
pt.lat = a[i].getDouble("lat");
pt.lon = a[i].getDouble("lon");
if (jobj.has("name")) {
pt.name = jobj.getString("name");
}
if (jobj.has("color")) {
pt.setColor(Algorithms.parseColor(jobj.getString("color")));
}
if (jobj.has("description")) {
pt.desc = jobj.getString("description");
}
if (jobj.has("created")) {
pt.getExtensionsToWrite().put("created", a[i].getLong("created") + "");
}
if (jobj.has("visible")) {
pt.getExtensionsToWrite().put("visible", a[i].getBoolean("visible") + "");
}
}
if (jobj.has("u")) {
pt.getExtensionsToWrite().put("u", String.valueOf(a[i].getLong("u")));
}
points.add(pt);
}
if (points.size() > 0) {
plugin.getSaveGpxTask(gr.name + " points", modify, false, isGroupConnect)
.execute(points.toArray(new WptPt[points.size()]));
}
}
if (deleteUsers) {
for (OsMoDevice s : toDelete.values()) {
s.deleted = System.currentTimeMillis();
delta.add(s);
}
}
} catch (JSONException e) {
e.printStackTrace();
service.showErrorMessage(e.getMessage());
}
return delta;
}
public String createGroup(String groupName, boolean onlyByInvite, String description, String policy) {
Protocol.CreateGroupData obj = new Protocol.CreateGroupData(groupName,
onlyByInvite, description, policy);
service.pushCommand("GROUP_CREATE|" + gson.toJson(obj));
return "GROUP_CREATE";
}
public void setGenColor(OsMoDevice device, int genColor) {
device.genColor = genColor;
storage.save();
}
public String joinGroup(String groupId, String userName, String nick) {
final String op = "GROUP_JOIN:" + groupId + "|" + nick;
OsMoGroup g = storage.getGroup(groupId);
if (g == null) {
g = new OsMoGroup();
g.groupId = groupId;
storage.addGroup(g);
}
g.userName = userName;
service.pushCommand(op);
return "GROUP_JOIN:" + groupId;
}
public String leaveGroup(OsMoGroup group) {
final String op = "GROUP_LEAVE:" + group.groupId;
storage.deleteGroup(group);
storage.save();
service.pushCommand(op);
if (group.isEnabled()) {
group.enabled = false;
disconnectAllGroupUsers(group);
}
return op;
}
public void setDeviceProperties(OsMoDevice model, String name, int color) {
model.userName = name;
model.userColor = color;
storage.save();
}
@Override
public String nextSendCommand(OsMoThread tracker) {
return null;
}
public void clearGroups() {
storage.clearGroups();
storage.save();
}
}