package net.osmand.plus.osmo; import android.app.Activity; import android.content.Intent; import android.os.AsyncTask; import android.os.Build; import android.view.View; import android.widget.ArrayAdapter; import net.osmand.IndexConstants; import net.osmand.Location; import net.osmand.PlatformUtil; import net.osmand.plus.ApplicationMode; import net.osmand.plus.ContextMenuAdapter; import net.osmand.plus.ContextMenuItem; import net.osmand.plus.GPXUtilities; import net.osmand.plus.GPXUtilities.GPXFile; import net.osmand.plus.GPXUtilities.WptPt; import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; import net.osmand.plus.NavigationService; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.dashboard.tools.DashFragmentData; import net.osmand.plus.download.DownloadFileHelper; import net.osmand.plus.osmo.OsMoService.SessionInfo; import net.osmand.plus.views.MapInfoLayer; import net.osmand.plus.views.OsmandMapLayer.DrawSettings; import net.osmand.plus.views.OsmandMapTileView; import net.osmand.plus.views.mapwidgets.TextInfoWidget; import net.osmand.util.Algorithms; import org.apache.commons.logging.Log; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; public class OsMoPlugin extends OsmandPlugin implements OsMoReactor { private OsmandApplication app; public static final String ID = "osmand.osmo"; private OsMoService service; private OsMoTracker tracker; private OsMoGroups groups; private TextInfoWidget osmoControl; private OsMoPositionLayer olayer; protected MapActivity mapActivity; protected Activity groupsActivity; protected OsMoControlDevice deviceControl; private final static Log LOG = PlatformUtil.getLog(OsMoPlugin.class); public OsMoPlugin(final OsmandApplication app) { this.app = app; } @Override public int getAssetResourceName() { return R.drawable.osmo_monitoring; } @Override public boolean init(final OsmandApplication app, Activity activity) { if (service == null) { service = new OsMoService(app, this); tracker = new OsMoTracker(service, app.getSettings().OSMO_SAVE_TRACK_INTERVAL, app.getSettings().OSMO_SEND_LOCATIONS_STATE); deviceControl = new OsMoControlDevice(app, this, service, tracker); groups = new OsMoGroups(this, service, tracker, app); } ApplicationMode.regWidgetVisibility("osmo_control", (ApplicationMode[]) null); if (app.getSettings().OSMO_AUTO_CONNECT.get() || (System.currentTimeMillis() - app.getSettings().OSMO_LAST_PING.get() < 5 * 60 * 1000)) { service.connect(true); } return true; } public Activity getGroupsActivity() { return groupsActivity; } public void setGroupsActivity(Activity groupsActivity) { this.groupsActivity = groupsActivity; } @Override public void disable(OsmandApplication app) { super.disable(app); if (app.getNavigationService() != null) { app.getNavigationService().stopIfNeeded(app, NavigationService.USED_BY_LIVE); } tracker.disableTracker(); service.disconnect(); } @Override public void updateLocation(Location location) { tracker.sendCoordinate(location); } @Override public String getDescription() { return app.getString(R.string.osmo_plugin_description); } @Override public String getName() { return app.getString(R.string.osmo_plugin_name); } @Override public int getLogoResourceId() { return R.drawable.ic_osmo_dark; } @Override public String getHelpFileName() { return "feature_articles/osmo-plugin.html"; } @Override public void updateLayers(OsmandMapTileView mapView, MapActivity activity) { if (isActive()) { if (olayer == null) { registerLayers(activity); } if (osmoControl == null) { registerSideWidget(activity); } } else { MapInfoLayer layer = activity.getMapLayers().getMapInfoLayer(); if (layer != null && osmoControl != null) { layer.removeSideWidget(osmoControl); osmoControl = null; layer.recreateControls(); } if (olayer != null) { activity.getMapView().removeLayer(olayer); olayer = null; } } } @Override public void registerLayers(MapActivity activity) { registerSideWidget(activity); if (olayer != null) { activity.getMapView().removeLayer(olayer); } olayer = new OsMoPositionLayer(activity, this); activity.getMapView().addLayer(olayer, 5.5f); } private void registerSideWidget(MapActivity activity) { MapInfoLayer layer = activity.getMapLayers().getMapInfoLayer(); if (layer != null) { osmoControl = createOsMoControl(activity); layer.registerSideWidget(osmoControl, R.drawable.ic_osmo_dark, R.string.osmo_control, "osmo_control", false, 31); layer.recreateControls(); } } @Override public void mapActivityPause(MapActivity activity) { groups.removeUiListener(olayer); mapActivity = null; } @Override public void mapActivityResume(MapActivity activity) { if (olayer != null) { groups.addUiListeners(olayer); } mapActivity = activity; } /** * creates (if it wasn't created previously) the control to be added on a MapInfoLayer that shows a monitoring state (recorded/stopped) */ private TextInfoWidget createOsMoControl(final MapActivity map) { final TextInfoWidget osmoControl = new TextInfoWidget(map) { long lastUpdateTime; private int blinkImg; @Override public boolean updateInfo(DrawSettings drawSettings) { String txt = "OsMo"; String subtxt = ""; SessionInfo si = getService().getCurrentSessionInfo(); if (si != null) { String uname = si.username; if (uname != null && uname.length() > 0) { if (uname.length() > 7) { for (int k = 4; k < uname.length(); k++) { if (!Character.isLetterOrDigit(uname.charAt(k)) && uname.charAt(k) != '.' && uname.charAt(k) != '-') { uname = uname.substring(0, k); break; } } if (uname.length() > 12) { uname = uname.substring(0, 12); } } if (uname.length() > 4) { txt = ""; subtxt = uname; } else { txt = uname; } } } boolean night = drawSettings != null && drawSettings.isNightMode(); int srcSignalinactive = !night ? R.drawable.widget_osmo_inactive_day : R.drawable.widget_osmo_inactive_night; int small = srcSignalinactive; //tracker.isEnabledTracker() ? srcSignalinactive : srcinactive; int big = srcSignalinactive; // tracker.isEnabledTracker() ? srcSignalinactive : srcinactive; long last = service.getLastCommandTime(); if (service.isActive()) { if (tracker.isEnabledTracker()) { small = night ? R.drawable.widget_osmo_connected_location_night : R.drawable.widget_osmo_connected_location_day; big = night ? R.drawable.widget_osmo_connected_location_night : R.drawable.widget_osmo_connected_location_day; } else { small = night ? R.drawable.widget_osmo_connected_night : R.drawable.widget_osmo_connected_day; big = night ? R.drawable.widget_osmo_connected_night : R.drawable.widget_osmo_connected_day; } } setText(txt, subtxt); if (blinkImg != small) { setImageDrawable(small); } if (last != lastUpdateTime) { lastUpdateTime = last; blink(big, small); } return true; } private void blink(int bigger, final int smaller) { blinkImg = smaller; setImageDrawable(bigger); map.getMyApplication().runInUIThread(new Runnable() { @Override public void run() { blinkImg = 0; setImageDrawable(smaller); } }, 500); } }; osmoControl.updateInfo(null); osmoControl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(map, OsMoGroupsActivity.class); map.startActivity(intent); } }); return osmoControl; } @Override public Class<? extends Activity> getSettingsActivity() { return SettingsOsMoActivity.class; } @Override public void registerOptionsMenuItems(final MapActivity mapActivity, ContextMenuAdapter helper) { helper.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.osmo_groups, mapActivity) .setIcon(R.drawable.ic_osmo_dark) .setListener(new ContextMenuAdapter.ItemClickListener() { @Override public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int itemId, int pos, boolean isChecked) { Intent intent = new Intent(mapActivity, OsMoGroupsActivity.class); mapActivity.startActivity(intent); return true; } }) .setPosition(6) .createItem()); } @Override public String getId() { return ID; } public OsMoGroups getGroups() { return groups; } public OsMoTracker getTracker() { return tracker; } public OsMoService getService() { return service; } public AsyncTask<WptPt, String, String> getSaveGpxTask(final String name, final long timestamp, final boolean generateToast, final boolean isGroupConnect) { return new AsyncTask<WptPt, String, String>() { protected void onProgressUpdate(String... values) { if (values != null && generateToast) { String t = ""; for (String s : values) { t += s + "\n"; } app.showToastMessage(t.trim()); } } @Override protected String doInBackground(WptPt... params) { final File fl = app.getAppPath(IndexConstants.GPX_INDEX_DIR + "/osmo"); if (!fl.exists()) { fl.mkdirs(); } File ps = new File(fl, name + ".gpx"); String errors = ""; boolean changed = false; if (!ps.exists() || (ps.lastModified() / 1000) != (timestamp / 1000)) { changed = true; GPXFile g; if (isGroupConnect) { g = new GPXFile(); } else { g = GPXUtilities.loadGPXFile(app, ps); } for (WptPt point : params) { if (point.deleted) { for (WptPt pointInTrack : g.points) { if (pointInTrack.getExtensionsToRead().get("u").equals( point.getExtensionsToRead().get("u"))) { g.points.remove(pointInTrack); } } } else { g.points.add(point); } } errors = GPXUtilities.writeGpxFile(ps, g, app); ps.setLastModified(timestamp); if (errors == null) { errors = ""; } if (generateToast) { publishProgress(app.getString(R.string.osmo_gpx_points_downloaded, name)); } } SelectedGpxFile byPath = app.getSelectedGpxHelper().getSelectedFileByPath(ps.getAbsolutePath()); if (byPath == null || changed) { GPXFile selectGPXFile = GPXUtilities.loadGPXFile(app, ps); if (byPath != null) { app.getSelectedGpxHelper().selectGpxFile(selectGPXFile, false, false); } app.getSelectedGpxHelper().setGpxFileToDisplay(selectGPXFile); } return errors; } @Override protected void onPostExecute(String result) { if (result.length() > 0 && generateToast) { app.showToastMessage(app.getString(R.string.osmo_io_error) + result); } } }; } public AsyncTask<JSONObject, String, String> getDownloadGpxTask(final boolean makeVisible) { return new AsyncTask<JSONObject, String, String>() { @Override protected String doInBackground(JSONObject... params) { final File fl = app.getAppPath(IndexConstants.GPX_INDEX_DIR + "/osmo"); if (!fl.exists()) { fl.mkdirs(); } String errors = ""; for (JSONObject obj : params) { try { File f = new File(fl, obj.getString("name") + ".gpx"); long timestamp = obj.getLong("created") * 1000; int color = 0; if (obj.has("color")) { try { color = Algorithms.parseColor(obj.getString("color")); } catch (RuntimeException e) { LOG.warn("", e); } } boolean visible = obj.has("visible"); boolean changed = false; if (!f.exists() || (f.lastModified() != timestamp)) { long len = !f.exists() ? -1 : f.length(); boolean sizeEqual = obj.has("size") && obj.getLong("size") == len; boolean modifySupported = f.setLastModified(timestamp - 1); if (!sizeEqual || modifySupported) { changed = true; String url = obj.getString("url"); LOG.info("Download gpx " + url); DownloadFileHelper df = new DownloadFileHelper(app); InputStream is = df.getInputStreamToDownload(new URL(url), false); int av = is.available(); if (av > 0 && !modifySupported && len == av) { // ignore is.close(); } else { redownloadFile(f, timestamp, color, is); publishProgress(app.getString(R.string.osmo_gpx_track_downloaded, obj.getString("name"))); } } } if (visible && (changed || makeVisible)) { GPXFile selectGPXFile = GPXUtilities.loadGPXFile(app, f); if (color != 0) { selectGPXFile.setColor(color); } app.getSelectedGpxHelper().setGpxFileToDisplay(selectGPXFile); } } catch (JSONException | IOException e) { e.printStackTrace(); errors += e.getMessage() + "\n"; } } return errors; } private void redownloadFile(File f, long timestamp, int color, InputStream is) throws IOException { FileOutputStream fout = new FileOutputStream(f); byte[] buf = new byte[1024]; int k; while ((k = is.read(buf)) >= 0) { fout.write(buf, 0, k); } fout.close(); is.close(); if (!f.setLastModified(timestamp)) { LOG.error("Timestamp updates are not supported"); } } protected void onProgressUpdate(String... values) { if (values != null) { String t = ""; for (String s : values) { t += s + "\n"; } app.showToastMessage(t.trim()); refreshMap(); } } @Override protected void onPostExecute(String result) { if (result.length() > 0) { app.showToastMessage(app.getString(R.string.osmo_io_error) + result); } } }; } public void refreshMap() { if (mapActivity != null) { mapActivity.getMapView().refreshMap(); } } @Override public boolean acceptCommand(String command, String id, String data, JSONObject obj, OsMoThread tread) { return false; } @Override public String nextSendCommand(OsMoThread tracker) { return null; } @Override public void onConnected() { if (groupsActivity instanceof OsMoGroupsActivity) { ((OsMoGroupsActivity) groupsActivity).handleConnect(); } } @Override public void onDisconnected(String msg) { if (groupsActivity instanceof OsMoGroupsActivity) { ((OsMoGroupsActivity) groupsActivity).handleDisconnect(msg); } } public boolean useHttps() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; } @Override public DashFragmentData getCardFragment() { return DashOsMoFragment.FRAGMENT_DATA; } }