<NME> CGeoMap.java <BEF> package cgeo.geocaching.maps; import cgeo.geocaching.CacheListActivity; import cgeo.geocaching.CachePopup; import cgeo.geocaching.CompassActivity; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.WaypointPopup; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.databinding.MapGoogleBinding; import cgeo.geocaching.downloader.DownloaderUtils; import cgeo.geocaching.enumerations.CoordinatesType; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.LoadFlags.RemoveFlag; import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.filters.core.GeocacheFilterContext; import cgeo.geocaching.location.Geopoint; import cgeo.geocaching.location.ProximityNotification; import cgeo.geocaching.location.Viewport; import cgeo.geocaching.location.WaypointDistanceInfo; import cgeo.geocaching.log.LoggingUI; import cgeo.geocaching.maps.google.v2.GoogleGeoPoint; import cgeo.geocaching.maps.google.v2.GoogleMapProvider; import cgeo.geocaching.maps.google.v2.GooglePositionAndHistory; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapActivityImpl; import cgeo.geocaching.maps.interfaces.MapControllerImpl; import cgeo.geocaching.maps.interfaces.MapItemFactory; import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnCacheTapListener; import cgeo.geocaching.maps.interfaces.OnMapDragListener; import cgeo.geocaching.maps.mapsforge.MapsforgeMapProvider; import cgeo.geocaching.maps.mapsforge.v6.NewMap; import cgeo.geocaching.maps.routing.RoutingMode; import cgeo.geocaching.models.Geocache; import cgeo.geocaching.models.IWaypoint; import cgeo.geocaching.models.IndividualRoute; import cgeo.geocaching.models.Route; import cgeo.geocaching.models.RouteItem; import cgeo.geocaching.models.TrailHistoryElement; import cgeo.geocaching.models.Waypoint; import cgeo.geocaching.network.AndroidBeam; import cgeo.geocaching.permission.PermissionHandler; import cgeo.geocaching.permission.PermissionRequestContext; import cgeo.geocaching.permission.RestartLocationPermissionGrantedCallback; import cgeo.geocaching.sensors.GeoData; import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.Sensors; import cgeo.geocaching.service.CacheDownloaderService; import cgeo.geocaching.service.GeocacheChangedBroadcastReceiver; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.storage.DataStore; import cgeo.geocaching.ui.ViewUtils; import cgeo.geocaching.ui.WeakReferenceHandler; import cgeo.geocaching.utils.AndroidRxUtils; import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.ApplicationSettings; import cgeo.geocaching.utils.CompactIconModeUtils; import cgeo.geocaching.utils.FilterUtils; import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.HistoryTrackUtils; import cgeo.geocaching.utils.LeastRecentlyUsedSet; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MapMarkerUtils; import static cgeo.geocaching.location.Viewport.containingGCliveCaches; import java.util.List; import java.util.Map; import java.util.Set; /** * Class representing the Map in c:geo import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.CheckBox; import android.widget.ImageSwitcher; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.ProgressBar; import android.widget.Toast; import android.widget.ViewSwitcher.ViewFactory; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.google.android.gms.maps.model.LatLng; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; /** * Class representing the Map in c:geo */ public class CGeoMap extends AbstractMap implements ViewFactory, OnCacheTapListener { /** * max. number of caches displayed in the Live Map */ public static final int MAX_CACHES = 500; /** * initialization with an empty subscription to make static code analysis tools more happy */ private final CompositeDisposable resumeDisposables = new CompositeDisposable(); /** * Handler Messages */ private static final int HIDE_PROGRESS = 0; private static final int SHOW_PROGRESS = 1; private static final int UPDATE_TITLE = 0; private static final int INVALIDATE_MAP = 1; private static final String BUNDLE_MAP_SOURCE = "mapSource"; private static final String BUNDLE_MAP_STATE = "mapState"; private static final String BUNDLE_LIVE_ENABLED = "liveEnabled"; private static final String BUNDLE_PROXIMITY_NOTIFICATION = "proximityNotification"; private static final String BUNDLE_ROUTE = "route"; private static final String BUNDLE_FILTERCONTEXT = "filterContext"; private int zoom = -100; // threads private LoadTimer loadTimer = null; private UsersTimer usersTimer = null; //FIXME should be members of UsersTimer since started by it. private UsersThread usersThread = null; private DisplayUsersThread displayUsersThread = null; //FIXME move to OnOptionsItemSelected private LoadDetails loadDetailsThread = null; /** Time of last {@link LoadThread} run */ private volatile long loadThreadRun = 0L; /** Time of last {@link UsersThread} run */ private volatile long usersThreadRun = 0L; //Interthread communication flag private volatile boolean downloaded = false; // overlays private CachesOverlay overlayCaches = null; private OtherCachersOverlay overlayOtherCachers = null; private ScaleOverlay overlayScale = null; private PositionOverlay overlayPosition = null; // data for overlays private Viewport lastViewport = null; // map status data private static boolean followMyLocation = true; // threads private Disposable loadTimer; /** * Time of last {@link LoadRunnable} run */ private int cachesCnt = 0; /** List of caches in the viewport */ private final LRUList<cgCache> caches = new LRUList<cgCache>(MAX_CACHES); /** List of users in the viewport */ private List<Go4CacheUser> users = new ArrayList<Go4CacheUser>(); private List<cgCoord> coordinates = new ArrayList<cgCoord>(); // storing for offline private ProgressDialog waitDialog = null; private int detailTotal = 0; */ private int cachesCnt = 0; /** * List of waypoints in the viewport */ private final LeastRecentlyUsedSet<Waypoint> waypoints = new LeastRecentlyUsedSet<>(MAX_CACHES); private ProgressBar spinner; private boolean centered = false; // if map is already centered private boolean alreadyCentered = false; // -""- for setting my location private static Set<String> dirtyCaches = null; // handlers /** Updates the titles */ final private Handler displayHandler = new Handler() { private MapOptions mapOptions; // handlers /** * Updates the titles */ private static final class DisplayHandler extends WeakReferenceHandler<CGeoMap> { DisplayHandler(@NonNull final CGeoMap map) { super(map); } @Override public void handleMessage(final Message msg) { final CGeoMap map = getReference(); if (map == null) { return; } final int what = msg.what; switch (what) { case UPDATE_TITLE: map.setTitle(); map.setSubtitle(); break; case INVALIDATE_MAP: map.mapView.repaintRequired(null); break; default: break; } } } private final Handler displayHandler = new DisplayHandler(this); private void setTitle() { /** Updates the progress. */ final private Handler showProgressHandler = new Handler() { @Override public void handleMessage(Message msg) { final int what = msg.what; if (what == HIDE_PROGRESS) { ActivityMixin.showProgress(activity, false); } else if (what == SHOW_PROGRESS) { ActivityMixin.showProgress(activity, true); } } }; if (geocache.getGeocode().equals(mapOptions.geocode)) { return geocache; } } } return null; } private void setSubtitle() { final String subtitle = calculateSubtitle(); if (StringUtils.isEmpty(subtitle)) { return; } getActionBar().setSubtitle(MapUtils.getColoredValue(subtitle)); } private String calculateSubtitle() { // count caches in the sub title countVisibleCaches(); if (!mapOptions.isLiveEnabled && mapOptions.mapMode == MapMode.SINGLE) { final Geocache cache = getSingleModeCache(); if (cache != null) { return Formatter.formatMapSubtitle(cache); } return ""; } final StringBuilder subtitle = new StringBuilder(); final int totalCount = caches.size(); if (cachesCnt != totalCount && Settings.isDebug()) { subtitle.append(cachesCnt).append('/').append(res.getQuantityString(R.plurals.cache_counts, totalCount, totalCount)); } else { subtitle.append(res.getQuantityString(R.plurals.cache_counts, cachesCnt, cachesCnt)); } if (Settings.isDebug() && lastSearchResult != null && StringUtils.isNotBlank(lastSearchResult.getUrl())) { subtitle.append(" [").append(lastSearchResult.getUrl()).append(']'); } return subtitle.toString(); } @NonNull private ActionBar getActionBar() { final ActionBar actionBar = activity.getSupportActionBar(); assert actionBar != null; return actionBar; } /** * Updates the progress. */ private static final class ShowProgressHandler extends Handler { private int counter = 0; @NonNull private final WeakReference<CGeoMap> mapRef; ShowProgressHandler(@NonNull final CGeoMap map) { this.mapRef = new WeakReference<>(map); } @Override public void handleMessage(final Message msg) { final int what = msg.what; if (what == SHOW_PROGRESS) { showProgress(true); counter++; } else if (what == HIDE_PROGRESS && --counter == 0) { showProgress(false); } } private void showProgress(final boolean show) { final CGeoMap map = mapRef.get(); if (map != null && map.spinner != null) { map.spinner.setVisibility(show ? View.VISIBLE : View.GONE); } } } private final Handler showProgressHandler = new ShowProgressHandler(this); /* Current source id */ private int currentSourceId; public CGeoMap(@NonNull final MapActivityImpl activity) { super(activity); } protected void countVisibleCaches() { final Viewport viewport = mapView.getViewport(); cachesCnt = viewport == null ? caches.size() : viewport.count(caches.getAsList()); } @Override public void onSaveInstanceState(@NonNull final Bundle outState) { mapView.onSaveInstanceState(outState); outState.putInt(BUNDLE_MAP_SOURCE, currentSourceId); outState.putParcelable(BUNDLE_MAP_STATE, currentMapState()); outState.putBoolean(BUNDLE_LIVE_ENABLED, mapOptions.isLiveEnabled); if (proximityNotification != null) { outState.putParcelable(BUNDLE_PROXIMITY_NOTIFICATION, proximityNotification); } if (individualRoute != null) { outState.putParcelable(BUNDLE_ROUTE, individualRoute); } if (mapOptions.filterContext != null) { outState.putParcelable(BUNDLE_FILTERCONTEXT, mapOptions.filterContext); } } @Override public void onLowMemory() { mapView.onLowMemory(); } protected Geopoint getIntentCoords() { if (mapOptions.coords != null) { return mapOptions.coords; } else if (mapOptions.geocode != null) { final Viewport bounds = DataStore.getBounds(mapOptions.geocode, false); if (bounds != null) { return bounds.center; } } return null; } protected Viewport getIntentViewport() { if (mapOptions.coords != null) { return new Viewport(mapOptions.coords); overlayPosition = mapView.createAddPositionOverlay(activity); } if (Settings.isPublicLoc() && overlayOtherCachers == null) { overlayOtherCachers = mapView.createAddUsersOverlay(activity, getResources().getDrawable(R.drawable.user_location)); } if (overlayCaches == null) { mapView.setMapSource(); mapView.setBuiltInZoomControls(true); mapView.displayZoomControls(true); mapView.setOnDragListener(new MapDragListener(this)); // initialize overlays mapView.clearOverlays(); mapView.setOnTapListener(this); final Geopoint savedCoords = mapOptions.coords; // remember for centerMap if (mapOptions.mapMode == MapMode.LIVE && savedCoords != null) { mapOptions.coords = null; // no direction line, even if enabled in settings followMyLocation = false; // do not center on GPS position, even if in LIVE mode } overlayPositionAndScale = mapView.createAddPositionAndScaleOverlay(activity.findViewById(R.id.distance1).getRootView(), getIntentCoords(), mapOptions.geocode); if (trailHistory != null) { overlayPositionAndScale.setHistory(trailHistory); } if (null == individualRoute) { individualRoute = new IndividualRoute(this::setNavigationTargetFromIndividualRoute); reloadIndividualRoute(); } else { individualRoute.updateRoute(overlayPositionAndScale); } CompactIconModeUtils.setCompactIconModeThreshold(getResources()); // prepare circular progress spinner spinner = (ProgressBar) activity.findViewById(R.id.map_progressbar); if (null != spinner) { spinner.setVisibility(View.GONE); } mapView.repaintRequired(null); final Viewport viewport = getIntentViewport(); if (mapOptions.geocode != null && viewport != null && !viewport.topRight.equals(viewport.bottomLeft)) { mapView.zoomToBounds(viewport, mapItemFactory.getGeoPointBase(viewport.center)); } else { setZoom(Settings.getMapZoom(mapOptions.mapMode)); mapView.getMapController().setCenter(Settings.getMapCenter()); } if (mapOptions.mapState == null) { followMyLocation = followMyLocation && (mapOptions.mapMode == MapMode.LIVE); mapView.setCircles(Settings.isShowCircles()); } else { followMyLocation = mapOptions.mapState.followsMyLocation(); if (mapView.getCircles() != mapOptions.mapState.showsCircles()) { mapView.setCircles(mapOptions.mapState.showsCircles()); } } if (mapOptions.geocode != null || mapOptions.searchResult != null || savedCoords != null || mapOptions.mapState != null) { centerMap(mapOptions.geocode, mapOptions.searchResult, savedCoords, mapOptions.mapState); } myLocSwitch.setOnClickListener(new MyLocationListener()); switchMyLocationButton(); // removed startTimer since onResume is always called prepareFilterBar(); } } } } @Override public void onCreate(final Bundle savedInstanceState) { ApplicationSettings.setLocale(this.getActivity()); super.onCreate(savedInstanceState); // class init res = this.getResources(); activity = this.getActivity(); MapsforgeMapProvider.getInstance().updateOfflineMaps(); // hard coded for now to mitigate #9619 - GoogleMaps is the only map type left supported by CGeoMap // final MapProvider mapProvider = Settings.getMapProvider(); final MapProvider mapProvider = GoogleMapProvider.getInstance(); mapItemFactory = mapProvider.getMapItemFactory(); // Get parameters from the intent final Bundle extras = activity.getIntent().getExtras(); mapOptions = new MapOptions(activity, extras); final ArrayList<TrailHistoryElement> trailHistory = null; // Get fresh map information from the bundle if any if (savedInstanceState != null) { currentSourceId = savedInstanceState.getInt(BUNDLE_MAP_SOURCE, Settings.getMapSource().getNumericalId()); mapOptions.mapState = savedInstanceState.getParcelable(BUNDLE_MAP_STATE); mapOptions.isLiveEnabled = savedInstanceState.getBoolean(BUNDLE_LIVE_ENABLED, false); mapOptions.filterContext = savedInstanceState.getParcelable(BUNDLE_FILTERCONTEXT); proximityNotification = savedInstanceState.getParcelable(BUNDLE_PROXIMITY_NOTIFICATION); caches.add(cache); } dirtyCaches.clear(); // force an update of the display. Includes a call to DownloadThread :-( // liveChanged = true; } startTimer(); // adding the bottom navigation component is handled by {@link AbstractBottomNavigationActivity#setContentView} activity.setContentView(MapGoogleBinding.inflate(activity.getLayoutInflater()).getRoot()); // map settings popup activity.findViewById(R.id.map_settings_popup).setOnClickListener(v -> loadTimer = null; } if (usersTimer != null) { usersTimer.stopIt(); usersTimer = null; } if (dir != null) { } ActivityMixin.onCreate(activity, true); // set layout ActivityMixin.setTheme(activity); setTitle(); // initialize map mapView = (MapViewImpl) activity.findViewById(mapProvider.getMapViewId()); // only add cache if it is currently visible activity.getLifecycle().addObserver(new GeocacheChangedBroadcastReceiver(mapView.getContext()) { @Override protected void onReceive(final Context context, final String geocode) { final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); // only add cache if it is currently visible loadTimer = null; } if (usersTimer != null) { usersTimer.stopIt(); usersTimer = null; } if (dir != null) { // solution from http://stackoverflow.com/questions/13900322/badparcelableexception-in-google-maps-code/15572337#15572337 if (savedInstanceState != null) { savedInstanceState.remove(BUNDLE_MAP_SOURCE); savedInstanceState.remove(BUNDLE_MAP_STATE); } mapView.onCreate(savedInstanceState); mapView.setListId(mapOptions.fromList); mapView.onMapReady(() -> initializeMap(trailHistory)); FilterUtils.initializeFilterBar(activity, mapActivity); MapUtils.updateFilterBar(activity, mapOptions.filterContext); AndroidBeam.disable(activity); MapUtils.showMapOneTimeMessages(activity, mapOptions.mapMode); MapsforgeMapProvider.getInstance().updateOfflineMaps(); } loadTimer = null; } if (usersTimer != null) { usersTimer.stopIt(); usersTimer = null; } if (dir != null) { mapActivity.getRouteTrackUtils().updateRouteTrackButtonVisibility(activity.findViewById(R.id.container_individualroute), individualRoute, mapActivity.getTracks()); overlayPositionAndScale.repaintRequired(); } private void setNavigationTargetFromIndividualRoute(@Nullable final Geopoint geopoint, final String geocode) { if (geopoint != null) { setTarget(geopoint, geocode); } } private void initMyLocationSwitchButton(final CheckBox locSwitch) { myLocSwitch = locSwitch; /* TODO: Switch back to ImageSwitcher for animations? myLocSwitch.setFactory(this); myLocSwitch.setInAnimation(activity, android.R.anim.fade_in); myLocSwitch.setOutAnimation(activity, android.R.anim.fade_out); */ myLocSwitch.setOnClickListener(new MyLocationListener(this)); switchMyLocationButton(); } /** * Set the zoom of the map. The zoom is restricted to a certain minimum in case of live map. */ private void setZoom(final int zoom) { mapView.getMapController().setZoom(mapOptions.isLiveEnabled ? Math.max(zoom, MIN_LIVEMAP_ZOOM) : zoom); } private void resumeTrack(final String key, final boolean preventReloading) { final Tracks tracks = mapActivity.getTracks(); if (null == tracks && !preventReloading) { mapActivity.getRouteTrackUtils().reloadTrack(tracks.getTrackfile(key), this::setTrack); } else if (null != tracks && null != overlayPositionAndScale && overlayPositionAndScale instanceof GooglePositionAndHistory) { ((GooglePositionAndHistory) overlayPositionAndScale).updateRoute(key, tracks.getRoute(key)); } } @Override public void onResume() { super.onResume(); // resume location access PermissionHandler.executeIfLocationPermissionGranted(this.activity, new RestartLocationPermissionGrantedCallback(PermissionRequestContext.CGeoMap) { @Override public void executeAfter() { mapView.onResume(); resumeDisposables.addAll(geoDirUpdate.start(GeoDirHandler.UPDATE_GEODIR), startTimer()); } }); final List<String> toRefresh; synchronized (dirtyCaches) { toRefresh = new ArrayList<>(dirtyCaches); dirtyCaches.clear(); } if (!toRefresh.isEmpty()) { AndroidRxUtils.computationScheduler.scheduleDirect(() -> { for (final String geocode : toRefresh) { final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); if (cache != null) { // new collection type needs to remove first caches.remove(cache); // re-add to update the freshness caches.add(cache); } } displayExecutor.execute(new DisplayRunnable(CGeoMap.this)); }); } MapUtils.updateFilterBar(activity, mapOptions.filterContext); final Tracks tracks = mapActivity.getTracks(); if (tracks != null) { tracks.resumeAllTracks(this::resumeTrack); } } @Override public void onPause() { resumeDisposables.clear(); savePrefs(); mapView.destroyDrawingCache(); // do not clear cached items - BitmapDescriptoCache needs same Drawables, not new ones created // after cache is cleared, or TODO implement ComparableDrawable? MapMarkerUtils.clearCachedItems(); super.onPause(); mapView.onPause(); } @Override public void onDestroy() { super.onDestroy(); if (mapView != null) { // avoid occasionally NPE mapView.onDestroy(); } } @Override public boolean onCreateOptionsMenu(@NonNull final Menu menu) { super.onCreateOptionsMenu(menu); MapProviderFactory.addMapviewMenuItems(activity, menu); /* if we have an Actionbar find the my position toggle */ initMyLocationSwitchButton(MapProviderFactory.createLocSwitchMenuItem(activity, menu)); FilterUtils.initializeFilterMenu(activity, mapActivity); return true; } @Override public boolean onPrepareOptionsMenu(@NonNull final Menu menu) { super.onPrepareOptionsMenu(menu); if (mapOptions != null) { ViewUtils.extendMenuActionBarDisplayItemCount(getActivity(), menu); } for (final MapSource mapSource : MapProviderFactory.getMapSources()) { final MenuItem menuItem = menu.findItem(mapSource.getNumericalId()); if (menuItem != null) { menuItem.setVisible(mapSource.isAvailable()); } } try { final MenuItem itemMapLive = menu.findItem(R.id.menu_map_live); if (mapOptions.isLiveEnabled) { itemMapLive.setIcon(R.drawable.ic_menu_sync_enabled); itemMapLive.setTitle(res.getString(R.string.map_live_disable)); } else { itemMapLive.setIcon(R.drawable.ic_menu_sync_disabled); itemMapLive.setTitle(res.getString(R.string.map_live_enable)); } itemMapLive.setVisible(mapOptions.coords == null || mapOptions.mapMode == MapMode.LIVE); menu.findItem(R.id.menu_store_caches).setVisible(!isLoading() && CollectionUtils.isNotEmpty(getGeocodesForCachesInViewport())); menu.findItem(R.id.menu_theme_mode).setVisible(!GoogleMapProvider.isSatelliteSource(Settings.getMapSource())); menu.findItem(R.id.menu_as_list).setVisible(!isLoading() && caches.size() > 1); menu.findItem(R.id.menu_hint).setVisible(mapOptions.mapMode == MapMode.SINGLE); menu.findItem(R.id.menu_compass).setVisible(mapOptions.mapMode == MapMode.SINGLE); if (mapOptions.mapMode == MapMode.SINGLE) { LoggingUI.onPrepareOptionsMenu(menu, getSingleModeCache()); } HistoryTrackUtils.onPrepareOptionsMenu(menu); // TrackUtils.onPrepareOptionsMenu is in maps/google/v2/GoogleMapActivity only } catch (final RuntimeException e) { Log.e("CGeoMap.onPrepareOptionsMenu", e); } return true; } @Override public boolean onOptionsItemSelected(@NonNull final MenuItem item) { final int id = item.getItemId(); if (id == R.id.menu_map_rotation_off) { setMapRotation(item, Settings.MAPROTATION_OFF); } else if (id == R.id.menu_map_rotation_manual) { setMapRotation(item, Settings.MAPROTATION_MANUAL); } else if (id == R.id.menu_map_rotation_auto) { setMapRotation(item, Settings.MAPROTATION_AUTO); } else if (id == R.id.menu_filter) { getMapActivity().showFilterMenu(); } else if (id == R.id.menu_map_live) { mapOptions.isLiveEnabled = !mapOptions.isLiveEnabled; if (mapOptions.mapMode == MapMode.LIVE) { Settings.setLiveMap(mapOptions.isLiveEnabled); } if (mapOptions.isLiveEnabled) { mapOptions.isStoredEnabled = true; mapOptions.filterContext = new GeocacheFilterContext(GeocacheFilterContext.FilterType.LIVE); refreshMapData(false); } markersInvalidated = true; lastSearchResult = null; mapOptions.searchResult = null; ActivityMixin.invalidateOptionsMenu(activity); if (mapOptions.mapMode == MapMode.SINGLE) { setTarget(mapOptions.coords, mapOptions.geocode); } mapOptions.mapMode = MapMode.LIVE; mapOptions.title = StringUtils.EMPTY; updateMapTitle(); } else if (id == R.id.menu_store_caches) { CacheDownloaderService.downloadCaches(activity, getGeocodesForCachesInViewport(), false, false, () -> displayExecutor.execute(new DisplayRunnable(CGeoMap.this))); } else if (id == R.id.menu_theme_mode) { mapView.selectMapTheme(activity); } else if (id == R.id.menu_as_list) { CacheListActivity.startActivityMap(activity, new SearchResult(getGeocodesForCachesInViewport())); ActivityMixin.overrideTransitionToFade(activity); } else if (id == R.id.menu_hint) { menuShowHint(); } else if (id == R.id.menu_compass) { menuCompass(); } else if (LoggingUI.onMenuItemSelected(item, activity, getSingleModeCache(), null)) { return true; } else if (id == R.id.menu_check_routingdata) { final Viewport bb = mapView.getViewport(); MapUtils.checkRoutingData(activity, bb.bottomLeft.getLatitude(), bb.bottomLeft.getLongitude(), bb.topRight.getLatitude(), bb.topRight.getLongitude()); } else if (HistoryTrackUtils.onOptionsItemSelected(activity, id, () -> mapView.repaintRequired(overlayPositionAndScale instanceof GeneralOverlay ? ((GeneralOverlay) overlayPositionAndScale) : null), this::clearTrailHistory) || DownloaderUtils.onOptionsItemSelected(activity, id, false)) { return true; } else if (id == R.id.menu_routetrack) { mapActivity.getRouteTrackUtils().showPopup(individualRoute, this::setTarget); } else { final MapSource mapSource = MapProviderFactory.getMapSource(id); if (mapSource != null) { item.setChecked(true); changeMapSource(mapSource); } else { return super.onOptionsItemSelected(item); } } return true; } public void triggerLongTapContextMenu(final Point tapXY) { if (Settings.isLongTapOnMapActivated()) { MapUtils.createMapLongClickPopupMenu(activity, new Geopoint(overlayPositionAndScale.getLongTapLatLng().latitude, overlayPositionAndScale.getLongTapLatLng().longitude), tapXY.x, tapXY.y, individualRoute, overlayPositionAndScale, getCurrentTargetCache(), mapOptions) .setOnDismissListener(menu -> overlayPositionAndScale.resetLongTapLatLng()) .show(); } } @Override public void refreshMapData(final boolean circlesSwitched) { markersInvalidated = true; overlayPositionAndScale.repaintRequired(); if (circlesSwitched) { mapView.setCircles(Settings.isShowCircles()); mapView.repaintRequired(null); } MapUtils.updateFilterBar(activity, mapOptions.filterContext); } private void routingModeChanged(final RoutingMode newValue) { Settings.setRoutingMode(newValue); final Tracks tracks = mapActivity.getTracks(); if ((null != individualRoute && individualRoute.getNumSegments() > 0) || null != tracks) { Toast.makeText(activity, R.string.brouter_recalculating, Toast.LENGTH_SHORT).show(); } reloadIndividualRoute(); if (null != tracks) { try { AndroidRxUtils.andThenOnUi(Schedulers.computation(), () -> tracks.traverse((key, route) -> { route.calculateNavigationRoute(); ((GooglePositionAndHistory) overlayPositionAndScale).updateRoute(key, route); }), () -> mapView.repaintRequired(overlayPositionAndScale instanceof GeneralOverlay ? ((GeneralOverlay) overlayPositionAndScale) : null)); } catch (RejectedExecutionException e) { Log.e("CGeoMap.routingModeChanged: RejectedExecutionException: " + e.getMessage()); } } mapView.repaintRequired(overlayPositionAndScale instanceof GeneralOverlay ? ((GeneralOverlay) overlayPositionAndScale) : null); } private void compactIconModeChanged(final int newValue) { Settings.setCompactIconMode(newValue); markersInvalidated = true; mapView.repaintRequired(overlayPositionAndScale instanceof GeneralOverlay ? ((GeneralOverlay) overlayPositionAndScale) : null); } @Override public void setTrack(final String key, final Route route) { mapActivity.getTracks().setRoute(key, route); resumeTrack(key, null == route); mapActivity.getRouteTrackUtils().updateRouteTrackButtonVisibility(activity.findViewById(R.id.container_individualroute), individualRoute, mapActivity.getTracks()); } @Override public void centerOnPosition(final double latitude, final double longitude, final Viewport viewport) { followMyLocation = false; switchMyLocationButton(); mapView.zoomToBounds(viewport, new GoogleGeoPoint(new LatLng(latitude, longitude))); } @Override public void reloadIndividualRoute() { individualRoute.reloadRoute((route) -> { overlayPositionAndScale.updateIndividualRoute(route); mapActivity.getRouteTrackUtils().updateRouteTrackButtonVisibility(activity.findViewById(R.id.container_individualroute), individualRoute, mapActivity.getTracks()); mapView.repaintRequired(overlayPositionAndScale instanceof GeneralOverlay ? ((GeneralOverlay) overlayPositionAndScale) : null); }); } private void clearTrailHistory() { DataStore.clearTrailHistory(); overlayPositionAndScale.setHistory(new ArrayList<>()); mapView.repaintRequired(overlayPositionAndScale instanceof GeneralOverlay ? ((GeneralOverlay) overlayPositionAndScale) : null); ActivityMixin.showToast(activity, res.getString(R.string.map_trailhistory_cleared)); } @Override public void clearIndividualRoute() { individualRoute.clearRoute((route) -> { overlayPositionAndScale.repaintRequired(); mapActivity.getRouteTrackUtils().updateRouteTrackButtonVisibility(activity.findViewById(R.id.container_individualroute), individualRoute, mapActivity.getTracks()); ActivityMixin.showToast(activity, res.getString(R.string.map_individual_route_cleared)); }); } private void setMapRotation(final MenuItem item, final int rotation) { Settings.setMapRotation(rotation); if (null != overlayPositionAndScale) { overlayPositionAndScale.updateMapRotation(); } item.setChecked(true); } private void menuCompass() { final Geocache cache = getSingleModeCache(); if (cache != null) { CompassActivity.startActivityCache(this.getActivity(), cache); } } private void menuShowHint() { final Geocache cache = getSingleModeCache(); if (cache != null) { cache.showHintToast(getActivity()); } } /** * @return a non-null Set of geocodes corresponding to the caches that are shown on screen. */ @NonNull private Set<String> getGeocodesForCachesInViewport() { final Set<String> geocodes = new HashSet<>(); final List<Geocache> cachesProtected = caches.getAsList(); final Viewport viewport = mapView.getViewport(); if (viewport == null) { // fail safe check return Collections.emptySet(); } /** * Starts the {@link LoadTimer} and {@link UsersTimer}. */ public synchronized void startTimer() { return geocodes; } /** * Restart the current activity if the map provider has changed, or change the map source if needed. * * @param newSource the new map source, which can be the same as the current one * @return true if a restart is needed, false otherwise */ private boolean changeMapSource(@NonNull final MapSource newSource) { final MapSource oldSource = Settings.getMapSource(); } if (Settings.isPublicLoc()) { if (usersTimer != null) { usersTimer.stopIt(); usersTimer = null; } usersTimer = new UsersTimer(); usersTimer.start(); } } /** * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadThread}. */ private class LoadTimer extends Thread { private LoadThread loadThread = null; private DownloadThread downloadThread = new DownloadThread(0L, 0L, 0L, 0L); private DisplayThread displayThread = new DisplayThread(0L, 0L, 0L, 0L); public LoadTimer() { super("Load Timer"); } final View mapAttributionView = activity.findViewById(mapAttributionViewId); public void stopIt() { stop = true; if (loadThread != null) { loadThread.stopIt(); loadThread = null; } if (downloadThread != null) { downloadThread.stopIt(); downloadThread = null; } if (displayThread != null) { displayThread.stopIt(); displayThread = null; } } @Override public void run() { GeoPointImpl mapCenterNow; int centerLatitudeNow; int centerLongitudeNow; int spanLatitudeNow; int spanLongitudeNow; int zoomNow; boolean moved = false; boolean force = false; long currentTime = 0; while (!stop) { try { * Restart the current activity with the default map source. if (mapView != null) { // get current viewport mapCenterNow = mapView.getMapViewCenter(); centerLatitudeNow = mapCenterNow.getLatitudeE6(); centerLongitudeNow = mapCenterNow.getLongitudeE6(); spanLatitudeNow = mapView.getLatitudeSpan(); spanLongitudeNow = mapView.getLongitudeSpan(); // check if map moved or zoomed //TODO Portree Use Rectangle inside with bigger search window. That will stop reloading on every move moved = false; force = false; if (liveChanged) { moved = true; force = true; } else if (live && Settings.isLiveMap() && !downloaded) { moved = true; } else if (centerLatitude == null || centerLongitude == null) { if (mapCenter == null) { return null; } return new MapState(mapCenter.getCoords(), mapView.getMapZoomLevel(), followMyLocation, mapView.getCircles(), targetGeocode, lastNavTarget, mapOptions.isLiveEnabled, mapOptions.isStoredEnabled); } moved = true; } if (moved && caches != null && centerLatitude != null && centerLongitude != null && ((Math.abs(centerLatitudeNow - centerLatitude) > (spanLatitudeNow * 1.2)) || (Math.abs(centerLongitudeNow - centerLongitude) > (spanLongitudeNow * 1.2)))) { force = true; } // update title on any change zoomNow = mapView.getMapZoomLevel(); if (moved || zoomNow != zoom || spanLatitudeNow != spanLatitude || spanLongitudeNow != spanLongitude || centerLatitudeNow != centerLatitude || centerLongitudeNow != centerLongitude) { displayHandler.sendEmptyMessage(UPDATE_TITLE); } centerMap(geo.getCoords()); } } if (moved) { liveChanged = false; currentTime = System.currentTimeMillis(); if (1000 < (currentTime - loadThreadRun)) { // from web if (20000 < (currentTime - loadThreadRun)) { force = true; // probably stucked thread } if (force && loadThread != null && loadThread.isWorking()) { loadThread.stopIt(); try { sleep(100); } catch (Exception e) { // nothing } } if (loadThread != null && loadThread.isWorking()) { continue; } centerLatitude = centerLatitudeNow; centerLongitude = centerLongitudeNow; spanLatitude = spanLatitudeNow; spanLongitude = spanLongitudeNow; showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); loadThread = new LoadThread(centerLatitude, centerLongitude, spanLatitude, spanLongitude); loadThread.start(); //loadThread will kick off downloadThread once it's done } } } if (!isLoading()) { showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); } yield(); } catch (Exception e) { Log.w(Settings.tag, "cgeomap.LoadTimer.run: " + e.toString()); private final WeakReference<CGeoMap> mapRef; } } public void startNewDisplayThread(long centerLat, long centerLon, long spanLat, long spanLon) { if (displayThread.isWorking()) { // create new thread displayThread.stopIt(); displayThread = null; displayThread = new DisplayThread(centerLat, centerLon, spanLat, spanLon); displayThread.start(); } else { // reuse thread displayThread.setCenterSpan(centerLat, centerLon, spanLat, spanLon); displayThread.run(); } } public boolean isLoading() { if (loadThread != null && loadThread.isWorking()) { return true; } if (downloadThread != null && downloadThread.isWorking()) { return true; } if (displayThread != null && displayThread.isWorking()) { return true; } return false; } public void startNewDownloadThread(final long centerLat, final long centerLon, final long spanLat, final long spanLon) { if (downloadThread.isWorking()) { // create new thread downloadThread.stopIt(); downloadThread = null; downloadThread = new DownloadThread(centerLat, centerLon, spanLat, spanLon); downloadThread.start(); } else { // reuse thread downloadThread.setCenterSpan(centerLat, centerLon, spanLat, spanLon); downloadThread.run(); } } public void stopDisplayThread() { if (displayThread != null) { displayThread.stopIt(); } } } /** * Timer triggering every 250 ms to start the {@link UsersThread} for displaying user. */ private class UsersTimer extends Thread { public UsersTimer() { super("Users Timer"); } final CGeoMap map = mapRef.get(); public void stopIt() { stop = true; if (usersThread != null) { usersThread.stopIt(); usersThread = null; } if (displayUsersThread != null) { displayUsersThread.stopIt(); displayUsersThread = null; } } @Override public void run() { GeoPointImpl mapCenterNow; int centerLatitudeNow; int centerLongitudeNow; int spanLatitudeNow; int spanLongitudeNow; boolean moved = false; long currentTime = 0; while (!stop) { try { sleep(250); if (mapView != null) { // get current viewport mapCenterNow = mapView.getMapViewCenter(); centerLatitudeNow = mapCenterNow.getLatitudeE6(); centerLongitudeNow = mapCenterNow.getLongitudeE6(); spanLatitudeNow = mapView.getLatitudeSpan(); spanLongitudeNow = mapView.getLongitudeSpan(); // check if map moved or zoomed moved = false; currentTime = System.currentTimeMillis(); if (60000 < (currentTime - usersThreadRun)) { moved = true; } else if (centerLatitudeUsers == null || centerLongitudeUsers == null) { moved = true; final CGeoMap map = mapRef.get(); if (map == null || map.overlayPositionAndScale == null) { return false; } return Math.abs(AngleUtils.difference(currentHeading, map.overlayPositionAndScale.getHeading())) > MIN_HEADING_DELTA; } } // save new values if (moved && (1000 < (currentTime - usersThreadRun))) { if (usersThread != null && usersThread.isWorking()) { continue; } centerLatitudeUsers = centerLatitudeNow; centerLongitudeUsers = centerLongitudeNow; spanLatitudeUsers = spanLatitudeNow; spanLongitudeUsers = spanLongitudeNow; usersThread = new UsersThread(centerLatitudeNow, centerLongitudeNow, spanLatitudeNow, spanLongitudeNow); usersThread.start(); } } dist = currentLocation.distanceTo(lastLocation); } final float[] mapDimension = new float[1]; if (map.mapView.getWidth() < map.mapView.getHeight()) { final double span = map.mapView.getLongitudeSpan() / 1e6; } /** * Worker thread that loads caches and waypoints from the database and then spawns the {@link DownloadThread}. * started by {@link LoadTimer} */ private class LoadThread extends DoThread { public LoadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); setName("Load From Database"); } @Override public void run() { try { stop = false; working = true; loadThreadRun = System.currentTimeMillis(); if (stop) { displayHandler.sendEmptyMessage(UPDATE_TITLE); working = false; return; } // stage 1 - pull and render from the DB only for live map if (searchIntent != null || geocodeIntent != null) { // map started from another activity search = new SearchResult(searchIntent); } private static final class LoadTimerAction implements Runnable { @NonNull private final WeakReference<CGeoMap> mapRef; private int previousZoom = -100; private Viewport previousViewport; LoadTimerAction(@NonNull final CGeoMap map) { this.mapRef = new WeakReference<>(map); } downloaded = true; } if (stop) { displayHandler.sendEmptyMessage(UPDATE_TITLE); working = false; return; } caches.addAll(search.getCachesFromSearchResult(LoadFlags.LOAD_WAYPOINTS)); if (live) { try { // get current viewport final Viewport viewportNow = map.mapView.getViewport(); if (viewportNow == null) { return; } // Since zoomNow is used only for local comparison purposes, // it is ok to use the Google Maps compatible zoom level of OSM Maps } } if (stop) { displayHandler.sendEmptyMessage(UPDATE_TITLE); working = false; return; } //render if (loadTimer != null) { loadTimer.startNewDisplayThread(centerLat, centerLon, spanLat, spanLon); } if (stop) { if (loadTimer != null) { loadTimer.stopDisplayThread(); } displayHandler.sendEmptyMessage(UPDATE_TITLE); working = false; return; } //*** this needs to be in it's own thread // stage 2 - pull and render from geocaching.com //this should just fetch and insert into the db _and_ be cancel-able if the viewport changes if (live && Settings.isLiveMap()) { if (loadTimer != null) { loadTimer.startNewDownloadThread(centerLat, centerLon, spanLat, spanLon); } } } finally { working = false; } } } /** * Worker thread downloading caches from the internet. * Started by {@link LoadThread}. Duplicate Code with {@link UsersThread} */ private class DownloadThread extends DoThread { public DownloadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); setName("Download Caches"); } @Override public void run() { try { working = true; showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); // show progress double lat1 = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4); double lat2 = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4); double lon1 = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4); double lon2 = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4); double latMin = Math.min(lat1, lat2); double latMax = Math.max(lat1, lat2); double lonMin = Math.min(lon1, lon2); double lonMax = Math.max(lon1, lon2); //*** this needs to be in it's own thread // stage 2 - pull and render from geocaching.com //this should just fetch and insert into the db _and_ be cancel-able if the viewport changes int count = 0; do { if (stop) { throw new ThreadDeath(); } if (tokens == null) { tokens = GCBase.getTokens(); downloadExecutor.getActiveCount() > 0 || displayExecutor.getActiveCount() > 0); } } if (stop) { throw new ThreadDeath(); } final Viewport viewport = new Viewport(new Geopoint(latMin, lonMin), new Geopoint(latMax, lonMax)); search = ConnectorFactory.searchByViewport(viewport, tokens); if (search != null) { downloaded = true; private static class LoadRunnable extends DoRunnable { LoadRunnable(@NonNull final CGeoMap map) { super(map); } @Override public void runWithMap(final CGeoMap map) { } while (count < 2); if (stop) { throw new ThreadDeath(); } if (search != null) { Set<cgCache> result = search.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); // to update the caches they have to be removed first showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); caches.addAll(result); } if (stop) { throw new ThreadDeath(); } //render if (loadTimer != null) { loadTimer.startNewDisplayThread(centerLat, centerLon, spanLat, spanLon); } } catch (ThreadDeath e) { Log.d(Settings.tag, "DownloadThread stopped"); displayHandler.sendEmptyMessage(UPDATE_TITLE); } finally { stop = false; working = false; showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress } } } /** * Thread to Display (down)loaded caches. Started by {@link LoadThread} and {@link DownloadThread} */ private class DisplayThread extends DoThread { public DisplayThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); setName("Display Thread"); } @Override public void run() { try { working = true; if (mapView == null || caches == null) { throw new ThreadDeath(); } // we don't want to see any stale waypoints waypoints.clear(); if (cachesCnt < Settings.getWayPointsThreshold() || mapOptions.geocode != null) { if (mapOptions.isLiveEnabled || mapMode == MapMode.LIVE || mapMode == MapMode.COORDS) { if (!cachesToDisplay.isEmpty()) { for (cgCache cache : cachesToDisplay) { if (stop) { throw new ThreadDeath(); } if (cache.getCoords() == null) { continue; for (final Geocache c : caches.getAsList()) { final Set<Waypoint> filteredWaypoints = new HashSet<>(c.getWaypoints()); MapUtils.filter(filteredWaypoints, mapOptions.filterContext); waypoints.addAll(filteredWaypoints); } } } //render displayExecutor.execute(new DisplayRunnable(this)); if (mapOptions.isLiveEnabled) { downloadExecutor.execute(new DownloadRunnable(this)); } lastSearchResult = searchResult; } finally { showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress } } cachesCnt = cachesToDisplay.size(); if (stop) { throw new ThreadDeath(); } } else { overlayCaches.updateItems(itemsToDisplay); displayHandler.sendEmptyMessage(INVALIDATE_MAP); } cachesToDisplay.clear(); displayHandler.sendEmptyMessage(UPDATE_TITLE); } catch (ThreadDeath e) { Log.d(Settings.tag, "DisplayThread stopped"); displayHandler.sendEmptyMessage(UPDATE_TITLE); } finally { stop = false; working = false; } } } /** * Thread to load users from Go 4 Cache * Duplicate Code with {@link DownloadThread} */ private class UsersThread extends DoThread { public UsersThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); setName("Load Users From Go4Cache"); } @Override // update the caches final Viewport viewport = new Viewport(center, spanLat / 1e6 * 1.5, spanLon / 1e6 * 1.5); try { working = true; usersThreadRun = System.currentTimeMillis(); if (stop) { return; } users = Go4Cache.getGeocachersInViewport(Settings.getUsername(), viewport); if (stop) { return; } if (displayUsersThread != null && displayUsersThread.isWorking()) { displayUsersThread.stopIt(); } displayUsersThread = new DisplayUsersThread(users, centerLat, centerLon, spanLat, spanLon); displayUsersThread.start(); } finally { working = false; } } } /** * Thread to display users of Go 4 Cache started from {@link UsersThread} */ private class DisplayUsersThread extends DoThread { private List<Go4CacheUser> users = null; public DisplayUsersThread(List<Go4CacheUser> usersIn, long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); setName("Display Users"); users = usersIn; } @Override public void run() { try { working = true; if (mapView == null || CollectionUtils.isEmpty(users)) { return; } private static class DisplayRunnable extends DoRunnable { DisplayRunnable(@NonNull final CGeoMap map) { super(map); } OtherCachersOverlayItemImpl item = null; for (Go4CacheUser userOne : users) { if (stop) { return; } if (userOne.getCoords() == null) { continue; } private void doDisplayRun() { // don't add anything to mapView if just one point should be displayed counter++; if ((counter % 10) == 0) { overlayOtherCachers.updateItems(items); displayHandler.sendEmptyMessage(INVALIDATE_MAP); } } overlayOtherCachers.updateItems(items); } finally { working = false; } } } // when less than showWaypointsthreshold Caches shown countVisibleCaches(); final boolean forceCompactIconMode = CompactIconModeUtils.forceCompactIconMode(cachesCnt); if (mapOptions.mapMode == MapMode.SINGLE || cachesCnt < Settings.getWayPointsThreshold()) { for (final Waypoint waypoint : waypointsToDisplay) { if (waypoint != null && waypoint.getCoords() != null && waypoint.getCoords().isValid()) { itemsToDisplay.add(getWaypointItem(waypoint, forceCompactIconMode)); } } } for (final Geocache cache : cachesToDisplay) { if (cache != null && cache.getCoords() != null && cache.getCoords().isValid()) { itemsToDisplay.add(getCacheItem(cache, forceCompactIconMode)); } } } // don't add other waypoints to overlayCaches if just one point should be displayed if (mapOptions.coords == null) { mapView.updateItems(itemsToDisplay); } displayHandler.sendEmptyMessage(INVALIDATE_MAP); updateMapTitle(); } finally { showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); } } private void displayPoint(final Geopoint coords) { if (!coords.isValid()) { } } /** * Abstract Base Class for the worker threads. */ private abstract static class DoThread extends Thread { protected boolean working = false; protected boolean stop = false; protected long centerLat = 0L; protected long centerLon = 0L; protected long spanLat = 0L; protected long spanLon = 0L; public DoThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { setCenterSpan(centerLatIn, centerLonIn, spanLatIn, spanLonIn); } public void setCenterSpan(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { centerLat = centerLatIn; centerLon = centerLonIn; spanLat = spanLatIn; spanLon = spanLonIn; } public synchronized boolean isWorking() { return working; } public synchronized void stopIt() { stop = true; } } /** protected DoRunnable(@NonNull final CGeoMap map) { mapRef = new WeakReference<>(map); } @Override public final void run() { final CGeoMap map = mapRef.get(); if (map != null) { runWithMap(map); } } protected abstract void runWithMap(CGeoMap map); } private static boolean mapMoved(final Viewport referenceViewport, final Viewport newViewport) { return Math.abs(newViewport.getLatitudeSpan() - referenceViewport.getLatitudeSpan()) > 50e-6 || Math.abs(newViewport.getLongitudeSpan() - referenceViewport.getLongitudeSpan()) > 50e-6 || Math.abs(newViewport.center.getLatitude() - referenceViewport.center.getLatitude()) > referenceViewport.getLatitudeSpan() / 4 || Math.abs(newViewport.center.getLongitude() - referenceViewport.center.getLongitude()) > referenceViewport.getLongitudeSpan() / 4; } // center map to desired location private void centerMap(final Geopoint coords) { if (coords == null || !coords.isValid()) { return; } final MapControllerImpl mapController = mapView.getMapController(); final GeoPointImpl target = makeGeoPoint(coords); if (alreadyCentered) { mapController.animateTo(target); } else { mapController.setCenter(target); } alreadyCentered = true; } // move map to view results of searchIntent private void centerMap(final String geocodeCenter, final SearchResult searchCenter, final Geopoint coordsCenter, final MapState mapState) { final MapControllerImpl mapController = mapView.getMapController(); if (!centered && mapState != null) { try { mapController.setCenter(mapItemFactory.getGeoPointBase(mapState.getCenter())); setZoom(mapState.getZoomLevel()); } catch (final RuntimeException e) { Log.e("centermap", e); } centered = true; alreadyCentered = true; } else if (!centered && coordsCenter != null) { try { mapController.setCenter(makeGeoPoint(coordsCenter)); } catch (final Exception e) { Log.e("centermap", e); } centered = true; alreadyCentered = true; } else if (!centered && (geocodeCenter != null || mapOptions.searchResult != null)) { try { Viewport viewport = null; if (geocodeCenter != null) { viewport = DataStore.getBounds(geocodeCenter); } else if (searchCenter != null) { viewport = DataStore.getBounds(searchCenter.getGeocodes()); } if (viewport == null) { return; } mapController.setCenter(mapItemFactory.getGeoPointBase(viewport.center)); if (viewport.getLatitudeSpan() != 0 && viewport.getLongitudeSpan() != 0) { mapController.zoomToSpan((int) (viewport.getLatitudeSpan() * 1e6), (int) (viewport.getLongitudeSpan() * 1e6)); } } catch (final RuntimeException e) { Log.e("centermap", e); } centered = true; alreadyCentered = true; } } // switch My Location button image private void switchMyLocationButton() { // FIXME: temporary workaround for the absence of "follow my location" on Android 3.x (see issue #4289). if (myLocSwitch != null) { myLocSwitch.setChecked(followMyLocation); if (followMyLocation) { myLocationInMiddle(Sensors.getInstance().currentGeo()); } } } // set my location listener private static class MyLocationListener implements View.OnClickListener { private final WeakReference<CGeoMap> mapRef; MyLocationListener(@NonNull final CGeoMap map) { mapRef = new WeakReference<>(map); } @Override public void onClick(final View view) { final CGeoMap map = mapRef.get(); if (map != null) { map.onFollowMyLocationClicked(); } } } private void onFollowMyLocationClicked() { followMyLocation = !followMyLocation; switchMyLocationButton(); } public static class MapDragListener implements OnMapDragListener { private final WeakReference<CGeoMap> mapRef; public MapDragListener(@NonNull final CGeoMap map) { mapRef = new WeakReference<>(map); } @Override public void onDrag() { final CGeoMap map = mapRef.get(); if (map != null) { map.onDrag(); } } } private void onDrag() { if (followMyLocation) { followMyLocation = false; switchMyLocationButton(); } } // make geopoint private GeoPointImpl makeGeoPoint(final Geopoint coords) { return mapItemFactory.getGeoPointBase(coords); } @Override public View makeView() { final ImageView imageView = new ImageView(activity); imageView.setScaleType(ScaleType.CENTER); imageView.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); return imageView; } public static void markCacheAsDirty(final String geocode) { synchronized (dirtyCaches) { dirtyCaches.add(geocode); } } private CachesOverlayItemImpl getCacheItem(final Geocache cache, final boolean isDotMode) { final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(cache, cache.applyDistanceRule()); if (isDotMode) { item.setMarker(MapMarkerUtils.getCacheDotMarker(getResources(), cache)); } else { item.setMarker(MapMarkerUtils.getCacheMarker(getResources(), cache, null)); } return item; } private CachesOverlayItemImpl getWaypointItem(final Waypoint waypoint, final boolean isDotMode) { final CachesOverlayItemImpl item = mapItemFactory.getCachesOverlayItem(waypoint, waypoint.applyDistanceRule()); if (isDotMode) { item.setMarker(MapMarkerUtils.getWaypointDotMarker(getResources(), waypoint)); } else { item.setMarker(MapMarkerUtils.getWaypointMarker(getResources(), waypoint, true)); } return item; } @Override public void onCacheTap(final IWaypoint waypoint) { if (waypoint == null) { return; } final CoordinatesType coordType = waypoint.getCoordType(); if (coordType == CoordinatesType.CACHE && StringUtils.isNotBlank(waypoint.getGeocode())) { final Geocache cache = DataStore.loadCache(waypoint.getGeocode(), LoadFlags.LOAD_CACHE_OR_DB); if (cache != null) { CGeoMap.markCacheAsDirty(cache.getGeocode()); CachePopup.startActivityAllowTarget(activity, cache.getGeocode()); } return; } if (coordType == CoordinatesType.WAYPOINT && waypoint.getId() >= 0) { CGeoMap.markCacheAsDirty(waypoint.getGeocode()); WaypointPopup.startActivityAllowTarget(getActivity(), waypoint.getId(), waypoint.getGeocode()); } } public WaypointDistanceInfo getClosestDistanceInM(final Geopoint coord) { int minDistance = 50000000; String name = ""; // check caches for (final Geocache item : caches) { final int distance = (int) (1000 * coord.distanceTo(item.getCoords())); if (distance > 0 && distance < minDistance) { minDistance = distance; name = item.getShortGeocode() + " " + item.getName(); } } // check waypoints for (final Waypoint item : waypoints) { final int distance = (int) (1000 * coord.distanceTo(item.getCoords())); if (distance > 0 && distance < minDistance) { minDistance = distance; name = item.getName() + " (" + item.getWaypointType().gpx + ")"; } } return new WaypointDistanceInfo(name, minDistance); } public Collection<Geocache> getCaches() { return caches; } @Override public GeocacheFilterContext getFilterContext() { return mapOptions.filterContext; } @Override public MapOptions getMapOptions() { return mapOptions; } } * @return */ private CachesOverlayItemImpl getItem(cgCoord coord, cgCache cache, cgWaypoint waypoint) { coordinates.add(coord); if (cache != null) { CachesOverlayItemImpl item = mapProvider.getCachesOverlayItem(coord, cache.getType()); <MSG> Medium rework. Fixes #1267, #1232, #711, #698 <DFF> @@ -71,6 +71,10 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * Class representing the Map in c:geo @@ -142,21 +146,17 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private int zoom = -100; // threads private LoadTimer loadTimer = null; - private UsersTimer usersTimer = null; - //FIXME should be members of UsersTimer since started by it. - private UsersThread usersThread = null; - private DisplayUsersThread displayUsersThread = null; - //FIXME move to OnOptionsItemSelected + private Go4CacheTimer go4CacheTimer = null; private LoadDetails loadDetailsThread = null; - /** Time of last {@link LoadThread} run */ + /** Time of last {@link LoadRunnable} run */ private volatile long loadThreadRun = 0L; - /** Time of last {@link UsersThread} run */ - private volatile long usersThreadRun = 0L; + /** Time of last {@link Go4CacheRunnable} run */ + private volatile long go4CacheThreadRun = 0L; //Interthread communication flag private volatile boolean downloaded = false; // overlays private CachesOverlay overlayCaches = null; - private OtherCachersOverlay overlayOtherCachers = null; + private OtherCachersOverlay overlayGo4Cache = null; private ScaleOverlay overlayScale = null; private PositionOverlay overlayPosition = null; // data for overlays @@ -171,9 +171,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private int cachesCnt = 0; /** List of caches in the viewport */ private final LRUList<cgCache> caches = new LRUList<cgCache>(MAX_CACHES); - /** List of users in the viewport */ - private List<Go4CacheUser> users = new ArrayList<Go4CacheUser>(); - private List<cgCoord> coordinates = new ArrayList<cgCoord>(); // storing for offline private ProgressDialog waitDialog = null; private int detailTotal = 0; @@ -187,6 +184,20 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private boolean centered = false; // if map is already centered private boolean alreadyCentered = false; // -""- for setting my location private static Set<String> dirtyCaches = null; + + // Thread pooling + BlockingQueue<Runnable> displayQueue = new ArrayBlockingQueue<Runnable>(1); + ThreadPoolExecutor displayExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, displayQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + BlockingQueue<Runnable> downloadQueue = new ArrayBlockingQueue<Runnable>(1); + ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, downloadQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + BlockingQueue<Runnable> loadQueue = new ArrayBlockingQueue<Runnable>(1); + ThreadPoolExecutor loadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, loadQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + BlockingQueue<Runnable> Go4CacheQueue = new ArrayBlockingQueue<Runnable>(1); + ThreadPoolExecutor Go4CacheExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, Go4CacheQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + BlockingQueue<Runnable> go4CacheDisplayQueue = new ArrayBlockingQueue<Runnable>(1); + ThreadPoolExecutor go4CacheDisplayExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, go4CacheDisplayQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); + + // handlers /** Updates the titles */ final private Handler displayHandler = new Handler() { @@ -235,14 +246,19 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto /** Updates the progress. */ final private Handler showProgressHandler = new Handler() { + private int counter = 0; + @Override public void handleMessage(Message msg) { final int what = msg.what; if (what == HIDE_PROGRESS) { - ActivityMixin.showProgress(activity, false); + if (--counter == 0) { + ActivityMixin.showProgress(activity, false); + } } else if (what == SHOW_PROGRESS) { ActivityMixin.showProgress(activity, true); + counter++; } } }; @@ -384,8 +400,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto overlayPosition = mapView.createAddPositionOverlay(activity); } - if (Settings.isPublicLoc() && overlayOtherCachers == null) { - overlayOtherCachers = mapView.createAddUsersOverlay(activity, getResources().getDrawable(R.drawable.user_location)); + if (Settings.isPublicLoc() && overlayGo4Cache == null) { + overlayGo4Cache = mapView.createAddUsersOverlay(activity, getResources().getDrawable(R.drawable.user_location)); } if (overlayCaches == null) { @@ -450,8 +466,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto myLocSwitch.setOnClickListener(new MyLocationListener()); switchMyLocationButton(); - // removed startTimer since onResume is always called - prepareFilterBar(); } @@ -492,8 +506,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto caches.add(cache); } dirtyCaches.clear(); - // force an update of the display. Includes a call to DownloadThread :-( - // liveChanged = true; + // Update display + GeoPointImpl mapCenterNow = mapView.getMapViewCenter(); + int centerLatitudeNow = mapCenterNow.getLatitudeE6(); + int centerLongitudeNow = mapCenterNow.getLongitudeE6(); + int spanLatitudeNow = mapView.getLatitudeSpan(); + int spanLongitudeNow = mapView.getLongitudeSpan(); + displayExecutor.execute(new DisplayRunnable(centerLatitudeNow, centerLongitudeNow, spanLatitudeNow, spanLongitudeNow)); } startTimer(); @@ -506,9 +525,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto loadTimer = null; } - if (usersTimer != null) { - usersTimer.stopIt(); - usersTimer = null; + if (go4CacheTimer != null) { + go4CacheTimer.stopIt(); + go4CacheTimer = null; } if (dir != null) { @@ -534,9 +553,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto loadTimer = null; } - if (usersTimer != null) { - usersTimer.stopIt(); - usersTimer = null; + if (go4CacheTimer != null) { + go4CacheTimer.stopIt(); + go4CacheTimer = null; } if (dir != null) { @@ -562,9 +581,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto loadTimer = null; } - if (usersTimer != null) { - usersTimer.stopIt(); - usersTimer = null; + if (go4CacheTimer != null) { + go4CacheTimer.stopIt(); + go4CacheTimer = null; } if (dir != null) { @@ -915,7 +934,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } /** - * Starts the {@link LoadTimer} and {@link UsersTimer}. + * Starts the {@link LoadTimer} and {@link Go4CacheTimer}. */ public synchronized void startTimer() { @@ -933,24 +952,21 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } if (Settings.isPublicLoc()) { - if (usersTimer != null) { - usersTimer.stopIt(); - usersTimer = null; + if (go4CacheTimer != null) { + go4CacheTimer.stopIt(); + go4CacheTimer = null; } - usersTimer = new UsersTimer(); - usersTimer.start(); + go4CacheTimer = new Go4CacheTimer(); + go4CacheTimer.start(); } } /** - * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadThread}. + * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadRunnable}. */ private class LoadTimer extends Thread { - private LoadThread loadThread = null; - private DownloadThread downloadThread = new DownloadThread(0L, 0L, 0L, 0L); - private DisplayThread displayThread = new DisplayThread(0L, 0L, 0L, 0L); - public LoadTimer() { + public LoadTimer() { super("Load Timer"); } @@ -958,34 +974,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto public void stopIt() { stop = true; - - if (loadThread != null) { - loadThread.stopIt(); - loadThread = null; - } - - if (downloadThread != null) { - downloadThread.stopIt(); - downloadThread = null; - } - - if (displayThread != null) { - displayThread.stopIt(); - displayThread = null; - } } @Override public void run() { - GeoPointImpl mapCenterNow; - int centerLatitudeNow; - int centerLongitudeNow; - int spanLatitudeNow; - int spanLongitudeNow; - int zoomNow; - boolean moved = false; - boolean force = false; - long currentTime = 0; while (!stop) { try { @@ -993,20 +985,18 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (mapView != null) { // get current viewport - mapCenterNow = mapView.getMapViewCenter(); - centerLatitudeNow = mapCenterNow.getLatitudeE6(); - centerLongitudeNow = mapCenterNow.getLongitudeE6(); - spanLatitudeNow = mapView.getLatitudeSpan(); - spanLongitudeNow = mapView.getLongitudeSpan(); + GeoPointImpl mapCenterNow = mapView.getMapViewCenter(); + int centerLatitudeNow = mapCenterNow.getLatitudeE6(); + int centerLongitudeNow = mapCenterNow.getLongitudeE6(); + int spanLatitudeNow = mapView.getLatitudeSpan(); + int spanLongitudeNow = mapView.getLongitudeSpan(); // check if map moved or zoomed //TODO Portree Use Rectangle inside with bigger search window. That will stop reloading on every move - moved = false; - force = false; + boolean moved = false; if (liveChanged) { moved = true; - force = true; } else if (live && Settings.isLiveMap() && !downloaded) { moved = true; } else if (centerLatitude == null || centerLongitude == null) { @@ -1020,12 +1010,8 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto moved = true; } - if (moved && caches != null && centerLatitude != null && centerLongitude != null && ((Math.abs(centerLatitudeNow - centerLatitude) > (spanLatitudeNow * 1.2)) || (Math.abs(centerLongitudeNow - centerLongitude) > (spanLongitudeNow * 1.2)))) { - force = true; - } - // update title on any change - zoomNow = mapView.getMapZoomLevel(); + int zoomNow = mapView.getMapZoomLevel(); if (moved || zoomNow != zoom || spanLatitudeNow != spanLatitude || spanLongitudeNow != spanLongitude || centerLatitudeNow != centerLatitude || centerLongitudeNow != centerLongitude) { displayHandler.sendEmptyMessage(UPDATE_TITLE); } @@ -1035,45 +1021,19 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (moved) { liveChanged = false; - currentTime = System.currentTimeMillis(); + long currentTime = System.currentTimeMillis(); if (1000 < (currentTime - loadThreadRun)) { - // from web - if (20000 < (currentTime - loadThreadRun)) { - force = true; // probably stucked thread - } - - if (force && loadThread != null && loadThread.isWorking()) { - loadThread.stopIt(); - - try { - sleep(100); - } catch (Exception e) { - // nothing - } - } - - if (loadThread != null && loadThread.isWorking()) { - continue; - } - centerLatitude = centerLatitudeNow; centerLongitude = centerLongitudeNow; spanLatitude = spanLatitudeNow; spanLongitude = spanLongitudeNow; - showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); - - loadThread = new LoadThread(centerLatitude, centerLongitude, spanLatitude, spanLongitude); - loadThread.start(); //loadThread will kick off downloadThread once it's done + loadExecutor.execute(new LoadRunnable(centerLatitude, centerLongitude, spanLatitude, spanLongitude)); } } } - if (!isLoading()) { - showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); - } - yield(); } catch (Exception e) { Log.w(Settings.tag, "cgeomap.LoadTimer.run: " + e.toString()); @@ -1081,61 +1041,21 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } - public void startNewDisplayThread(long centerLat, long centerLon, long spanLat, long spanLon) { - if (displayThread.isWorking()) { - // create new thread - displayThread.stopIt(); - displayThread = null; - displayThread = new DisplayThread(centerLat, centerLon, spanLat, spanLon); - displayThread.start(); - } else { - // reuse thread - displayThread.setCenterSpan(centerLat, centerLon, spanLat, spanLon); - displayThread.run(); - } - } - public boolean isLoading() { - if (loadThread != null && loadThread.isWorking()) { - return true; - } - if (downloadThread != null && downloadThread.isWorking()) { - return true; - } - if (displayThread != null && displayThread.isWorking()) { - return true; - } - return false; + return loadExecutor.getActiveCount() > 0 || + downloadExecutor.getActiveCount() > 0 || + displayExecutor.getActiveCount() > 0; } - public void startNewDownloadThread(final long centerLat, final long centerLon, final long spanLat, final long spanLon) { - if (downloadThread.isWorking()) { - // create new thread - downloadThread.stopIt(); - downloadThread = null; - downloadThread = new DownloadThread(centerLat, centerLon, spanLat, spanLon); - downloadThread.start(); - } else { - // reuse thread - downloadThread.setCenterSpan(centerLat, centerLon, spanLat, spanLon); - downloadThread.run(); - } - } - - public void stopDisplayThread() { - if (displayThread != null) { - displayThread.stopIt(); - } - } } /** - * Timer triggering every 250 ms to start the {@link UsersThread} for displaying user. + * Timer triggering every 250 ms to start the {@link Go4CacheRunnable} for displaying user. */ - private class UsersTimer extends Thread { + private class Go4CacheTimer extends Thread { - public UsersTimer() { + public Go4CacheTimer() { super("Users Timer"); } @@ -1143,46 +1063,28 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto public void stopIt() { stop = true; - - if (usersThread != null) { - usersThread.stopIt(); - usersThread = null; - } - - if (displayUsersThread != null) { - displayUsersThread.stopIt(); - displayUsersThread = null; - } } @Override public void run() { - GeoPointImpl mapCenterNow; - int centerLatitudeNow; - int centerLongitudeNow; - int spanLatitudeNow; - int spanLongitudeNow; - boolean moved = false; - long currentTime = 0; - while (!stop) { try { sleep(250); if (mapView != null) { // get current viewport - mapCenterNow = mapView.getMapViewCenter(); - centerLatitudeNow = mapCenterNow.getLatitudeE6(); - centerLongitudeNow = mapCenterNow.getLongitudeE6(); - spanLatitudeNow = mapView.getLatitudeSpan(); - spanLongitudeNow = mapView.getLongitudeSpan(); + GeoPointImpl mapCenterNow = mapView.getMapViewCenter(); + int centerLatitudeNow = mapCenterNow.getLatitudeE6(); + int centerLongitudeNow = mapCenterNow.getLongitudeE6(); + int spanLatitudeNow = mapView.getLatitudeSpan(); + int spanLongitudeNow = mapView.getLongitudeSpan(); // check if map moved or zoomed - moved = false; + boolean moved = false; - currentTime = System.currentTimeMillis(); + long currentTime = System.currentTimeMillis(); - if (60000 < (currentTime - usersThreadRun)) { + if (60000 < (currentTime - go4CacheThreadRun)) { moved = true; } else if (centerLatitudeUsers == null || centerLongitudeUsers == null) { moved = true; @@ -1195,18 +1097,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } // save new values - if (moved && (1000 < (currentTime - usersThreadRun))) { - if (usersThread != null && usersThread.isWorking()) { - continue; - } - + if (moved && (1000 < (currentTime - go4CacheThreadRun))) { centerLatitudeUsers = centerLatitudeNow; centerLongitudeUsers = centerLongitudeNow; spanLatitudeUsers = spanLatitudeNow; spanLongitudeUsers = spanLongitudeNow; - usersThread = new UsersThread(centerLatitudeNow, centerLongitudeNow, spanLatitudeNow, spanLongitudeNow); - usersThread.start(); + Go4CacheExecutor.execute(new Go4CacheRunnable(centerLatitudeNow, centerLongitudeNow, spanLatitudeNow, spanLongitudeNow)); } } @@ -1219,33 +1116,23 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } /** - * Worker thread that loads caches and waypoints from the database and then spawns the {@link DownloadThread}. + * Worker thread that loads caches and waypoints from the database and then spawns the {@link DownloadRunnable}. * started by {@link LoadTimer} */ - private class LoadThread extends DoThread { + private class LoadRunnable extends DoRunnable { - public LoadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + public LoadRunnable(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); - setName("Load From Database"); } @Override public void run() { try { - stop = false; - working = true; + showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); loadThreadRun = System.currentTimeMillis(); - if (stop) { - displayHandler.sendEmptyMessage(UPDATE_TITLE); - working = false; - - return; - } - // stage 1 - pull and render from the DB only for live map - if (searchIntent != null || geocodeIntent != null) { // map started from another activity search = new SearchResult(searchIntent); @@ -1265,13 +1152,6 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto downloaded = true; } - if (stop) { - displayHandler.sendEmptyMessage(UPDATE_TITLE); - working = false; - - return; - } - caches.addAll(search.getCachesFromSearchResult(LoadFlags.LOAD_WAYPOINTS)); if (live) { @@ -1286,81 +1166,36 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } - if (stop) { - displayHandler.sendEmptyMessage(UPDATE_TITLE); - working = false; - - return; - } - //render - if (loadTimer != null) { - loadTimer.startNewDisplayThread(centerLat, centerLon, spanLat, spanLon); - } - - if (stop) { - if (loadTimer != null) { - loadTimer.stopDisplayThread(); - } - displayHandler.sendEmptyMessage(UPDATE_TITLE); - working = false; - - return; - } - - //*** this needs to be in it's own thread - // stage 2 - pull and render from geocaching.com - //this should just fetch and insert into the db _and_ be cancel-able if the viewport changes + displayExecutor.execute(new DisplayRunnable(centerLat, centerLon, spanLat, spanLon)); if (live && Settings.isLiveMap()) { - if (loadTimer != null) { - loadTimer.startNewDownloadThread(centerLat, centerLon, spanLat, spanLon); - } + downloadExecutor.execute(new DownloadRunnable(centerLat, centerLon, spanLat, spanLon)); } } finally { - working = false; + showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress } } } /** * Worker thread downloading caches from the internet. - * Started by {@link LoadThread}. Duplicate Code with {@link UsersThread} + * Started by {@link LoadRunnable}. Duplicate Code with {@link Go4CacheRunnable} */ - private class DownloadThread extends DoThread { + private class DownloadRunnable extends DoRunnable { - public DownloadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + public DownloadRunnable(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); - setName("Download Caches"); } @Override public void run() { try { - working = true; showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); // show progress - double lat1 = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4); - double lat2 = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4); - double lon1 = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4); - double lon2 = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4); - - double latMin = Math.min(lat1, lat2); - double latMax = Math.max(lat1, lat2); - double lonMin = Math.min(lon1, lon2); - double lonMax = Math.max(lon1, lon2); - - - //*** this needs to be in it's own thread - // stage 2 - pull and render from geocaching.com - //this should just fetch and insert into the db _and_ be cancel-able if the viewport changes - int count = 0; do { - if (stop) { - throw new ThreadDeath(); - } if (tokens == null) { tokens = GCBase.getTokens(); @@ -1369,11 +1204,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } - if (stop) { - throw new ThreadDeath(); - } - - final Viewport viewport = new Viewport(new Geopoint(latMin,
Medium rework. Fixes #1267, #1232, #711, #698