Java tutorial
/* * Androzic - android navigation client that uses OziExplorer maps (ozf2, ozfx3). * Copyright (C) 2010-2014 Andrey Novikov <http://andreynovikov.info/> * * This file is part of Androzic application. * * Androzic is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Androzic is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Androzic. If not, see <http://www.gnu.org/licenses/>. */ package com.androzic; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.annotation.SuppressLint; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.location.Location; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v7.internal.view.SupportMenuInflater; import android.support.v7.internal.view.menu.MenuBuilder; import android.support.v7.internal.view.menu.MenuPopupHelper; import android.support.v7.internal.view.menu.MenuPresenter; import android.text.format.DateUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.AlphaAnimation; import android.view.animation.AnimationSet; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.androzic.data.Route; import com.androzic.data.Waypoint; import com.androzic.location.LocationService; import com.androzic.navigation.NavigationService; import com.androzic.route.OnRouteActionListener; import com.androzic.route.RouteEdit; import com.androzic.util.Astro; import com.androzic.util.Clipboard; import com.androzic.util.CoordinateParser; import com.androzic.util.StringFormatter; import com.androzic.waypoint.OnWaypointActionListener; public class MapFragment extends Fragment implements MapHolder, OnSharedPreferenceChangeListener, View.OnClickListener, View.OnTouchListener, MenuBuilder.Callback, MenuPresenter.Callback { private static final String TAG = "MapFragment"; private OnWaypointActionListener waypointActionsCallback; private OnRouteActionListener routeActionsCallback; // Settings /** * UI (map data) update interval in milliseconds */ private long updatePeriod; private boolean following; private boolean followOnLocation; private boolean keepScreenOn; private int showDistance; private boolean autoDim; private int dimInterval; private int dimValue; private boolean mapButtonsVisible; private int mapInfoHideDelay = Integer.MAX_VALUE; // never private int satInfoHideDelay = Integer.MAX_VALUE; // never private int navInfoHideDelay = Integer.MAX_VALUE; // never // Views private MapView map; private TextView coordinates; private TextView sattelites; private TextView currentFile; private TextView mapZoom; private TextView waypointName; private TextView waypointExtra; private TextView routeName; private TextView routeExtra; private TextView distanceValue; private TextView distanceUnit; private TextView bearingValue; private TextView bearingUnit; private TextView turnValue; private TextView turnUnit; private TextView speedValue; private TextView speedUnit; private TextView trackValue; private TextView trackUnit; private TextView elevationValue; private TextView elevationUnit; private TextView xtkValue; private TextView xtkUnit; private TextView waitBar; private View mapInfo; private View satInfo; private View navInfo; private View mapButtons; private ViewGroup dimView; private View anchor; Androzic application; private ExecutorService executorThread = Executors.newSingleThreadExecutor(); private FinishHandler finishHandler; private Handler updateCallback = new Handler(); private int waypointSelected = -1; private long mapObjectSelected = -1; protected long lastDim = 0; private boolean lastGeoid = true; private int zoom100X = 0; private int zoom100Y = 0; private boolean animationSet; public MapFragment() { application = Androzic.getApplication(); } @Override public void onCreate(Bundle savedInstanceState) { Log.e(TAG, "onCreate()"); super.onCreate(savedInstanceState); setRetainInstance(true); setHasOptionsMenu(true); finishHandler = new FinishHandler(this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.e(TAG, "onCreateView()"); View view = inflater.inflate(R.layout.fragment_map, container, false); coordinates = (TextView) view.findViewById(R.id.coordinates); sattelites = (TextView) view.findViewById(R.id.sats); currentFile = (TextView) view.findViewById(R.id.currentfile); mapZoom = (TextView) view.findViewById(R.id.currentzoom); waypointName = (TextView) view.findViewById(R.id.waypointname); waypointExtra = (TextView) view.findViewById(R.id.waypointextra); routeName = (TextView) view.findViewById(R.id.routename); routeExtra = (TextView) view.findViewById(R.id.routeextra); speedValue = (TextView) view.findViewById(R.id.speed); speedUnit = (TextView) view.findViewById(R.id.speedunit); trackValue = (TextView) view.findViewById(R.id.track); trackUnit = (TextView) view.findViewById(R.id.trackunit); elevationValue = (TextView) view.findViewById(R.id.elevation); elevationUnit = (TextView) view.findViewById(R.id.elevationunit); distanceValue = (TextView) view.findViewById(R.id.distance); distanceUnit = (TextView) view.findViewById(R.id.distanceunit); xtkValue = (TextView) view.findViewById(R.id.xtk); xtkUnit = (TextView) view.findViewById(R.id.xtkunit); bearingValue = (TextView) view.findViewById(R.id.bearing); bearingUnit = (TextView) view.findViewById(R.id.bearingunit); turnValue = (TextView) view.findViewById(R.id.turn); turnUnit = (TextView) view.findViewById(R.id.turnunit); // trackBar = (SeekBar) findViewById(R.id.trackbar); mapInfo = view.findViewById(R.id.mapinfo); satInfo = view.findViewById(R.id.satinfo); navInfo = view.findViewById(R.id.navinfo); mapButtons = view.findViewById(R.id.mapbuttons); waitBar = (TextView) view.findViewById(R.id.waitbar); anchor = view.findViewById(R.id.anchor); map = (MapView) view.findViewById(R.id.mapview); map.initialize(application, this); view.findViewById(R.id.zoomin).setOnClickListener(this); view.findViewById(R.id.zoomin).setOnTouchListener(this); view.findViewById(R.id.zoomout).setOnClickListener(this); view.findViewById(R.id.nextmap).setOnClickListener(this); view.findViewById(R.id.prevmap).setOnClickListener(this); coordinates.setOnClickListener(this); sattelites.setOnClickListener(this); currentFile.setOnClickListener(this); mapZoom.setOnClickListener(this); waypointName.setOnClickListener(this); // set route edit button actions view.findViewById(R.id.finishrouteedit).setOnClickListener(this); view.findViewById(R.id.addpoint).setOnClickListener(this); view.findViewById(R.id.insertpoint).setOnClickListener(this); view.findViewById(R.id.removepoint).setOnClickListener(this); view.findViewById(R.id.orderpoints).setOnClickListener(this); application.setMapHolder(this); return view; } @Override public void onAttach(Activity activity) { super.onAttach(activity); Log.e(TAG, "onAttach()"); dimView = new RelativeLayout(getActivity()); // This makes sure that the container activity has implemented // the callback interface. If not, it throws an exception try { waypointActionsCallback = (OnWaypointActionListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnWaypointActionListener"); } try { routeActionsCallback = (OnRouteActionListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnRouteActionListener"); } } @Override public void onStart() { super.onStart(); Log.e(TAG, "onStart()"); ((ViewGroup) getActivity().getWindow().getDecorView()).addView(dimView); } @Override public void onResume() { super.onResume(); Log.e(TAG, "onResume()"); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity()); onSharedPreferenceChanged(settings, getString(R.string.pref_maprenderinterval)); onSharedPreferenceChanged(settings, getString(R.string.pref_mapfollowonloc)); onSharedPreferenceChanged(settings, getString(R.string.pref_wakelock)); onSharedPreferenceChanged(settings, getString(R.string.pref_showdistance_int)); onSharedPreferenceChanged(settings, getString(R.string.pref_hidemapinfo)); onSharedPreferenceChanged(settings, getString(R.string.pref_hidesatinfo)); onSharedPreferenceChanged(settings, getString(R.string.pref_hidenavinfo)); onSharedPreferenceChanged(settings, getString(R.string.pref_mapdiminterval)); onSharedPreferenceChanged(settings, getString(R.string.pref_mapdimvalue)); onSharedPreferenceChanged(settings, getString(R.string.pref_mapdim)); onSharedPreferenceChanged(settings, getString(R.string.pref_unfollowontap)); onSharedPreferenceChanged(settings, getString(R.string.pref_lookahead)); onSharedPreferenceChanged(settings, getString(R.string.pref_mapbest)); onSharedPreferenceChanged(settings, getString(R.string.pref_mapbestinterval)); onSharedPreferenceChanged(settings, getString(R.string.pref_scalebarbg)); onSharedPreferenceChanged(settings, getString(R.string.pref_scalebarcolor)); onSharedPreferenceChanged(settings, getString(R.string.pref_scalebarbgcolor)); onSharedPreferenceChanged(settings, getString(R.string.pref_hidemapcross)); onSharedPreferenceChanged(settings, getString(R.string.pref_mapcrosscolor)); onSharedPreferenceChanged(settings, getString(R.string.pref_cursorvector)); onSharedPreferenceChanged(settings, getString(R.string.pref_cursorcolor)); onSharedPreferenceChanged(settings, getString(R.string.pref_navigation_proximity)); onSharedPreferenceChanged(settings, getString(R.string.pref_unitprecision)); onSharedPreferenceChanged(settings, getString(R.string.pref_unitspeed)); onSharedPreferenceChanged(settings, getString(R.string.pref_unitelevation)); onSharedPreferenceChanged(settings, getString(R.string.pref_unitangle)); PreferenceManager.getDefaultSharedPreferences(application).registerOnSharedPreferenceChangeListener(this); updateEditStatus(); updateGPSStatus(); onUpdateNavigationState(); onUpdateNavigationStatus(); application.registerReceiver(broadcastReceiver, new IntentFilter(NavigationService.BROADCAST_NAVIGATION_STATUS)); application.registerReceiver(broadcastReceiver, new IntentFilter(NavigationService.BROADCAST_NAVIGATION_STATE)); application.registerReceiver(broadcastReceiver, new IntentFilter(LocationService.BROADCAST_LOCATING_STATUS)); application.registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); application.registerReceiver(broadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON)); map.setKeepScreenOn(keepScreenOn); map.setFollowing(following); mapButtonsVisible = settings.getBoolean(getString(R.string.ui_mapbuttons_shown), true); mapButtons.setVisibility(mapButtonsVisible ? View.VISIBLE : View.GONE); updateMapViewArea(); // Start updating UI map.resume(); map.updateMapInfo(); map.updateMapCenter(); map.requestFocus(); updateCallback.post(updateUI); } @Override public void onPause() { super.onPause(); Log.e(TAG, "onPause()"); application.unregisterReceiver(broadcastReceiver); // Stop updating UI map.pause(); updateCallback.removeCallbacks(updateUI); PreferenceManager.getDefaultSharedPreferences(application).unregisterOnSharedPreferenceChangeListener(this); } @Override public void onStop() { super.onStop(); Log.e(TAG, "onStop()"); ((ViewGroup) getActivity().getWindow().getDecorView()).removeView(dimView); } @Override public void onDestroyView() { super.onDestroyView(); Log.e(TAG, "onDestroyView()"); map = null; coordinates = null; sattelites = null; currentFile = null; mapZoom = null; waypointName = null; waypointExtra = null; routeName = null; routeExtra = null; speedValue = null; speedUnit = null; trackValue = null; elevationValue = null; elevationUnit = null; distanceValue = null; distanceUnit = null; xtkValue = null; xtkUnit = null; bearingValue = null; turnValue = null; // trackBar = null; } @Override public void onDestroy() { super.onDestroy(); Log.e(TAG, "onDestroy()"); application = null; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.map_menu, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public void onPrepareOptionsMenu(final Menu menu) { boolean fixed = map != null && map.isFixed(); MenuItem follow = menu.findItem(R.id.action_follow); if (!fixed || following && map != null && !map.getStrictUnfollow()) { follow.setVisible(false); } else if (following) { follow.setIcon(R.drawable.ic_lock_outline_white_24dp); follow.setTitle(R.string.action_unfollow); } else { follow.setVisible(true); follow.setIcon(R.drawable.ic_lock_open_white_24dp); follow.setTitle(R.string.action_follow); } menu.findItem(R.id.action_locate).setVisible(!fixed); menu.findItem(R.id.action_locating).setChecked(application.isLocating()); menu.findItem(R.id.action_tracking).setChecked(application.isTracking()); boolean navigating = application.isNavigating(); boolean viaRoute = application.isNavigatingViaRoute(); menu.findItem(R.id.action_stop_navigation).setVisible(navigating); menu.findItem(R.id.action_next_nav_point).setVisible(viaRoute); menu.findItem(R.id.action_prev_nav_point).setVisible(viaRoute); if (viaRoute) { menu.findItem(R.id.action_next_nav_point) .setEnabled(application.navigationService.hasNextRouteWaypoint()); menu.findItem(R.id.action_prev_nav_point) .setEnabled(application.navigationService.hasPrevRouteWaypoint()); } } final private Runnable updateUI = new Runnable() { public void run() { updateCallback.postDelayed(this, updatePeriod); if (application.gpsEnabled) { if (!map.isFixed()) { sattelites.setText(R.string.sat_start); sattelites.setTextColor(getResources().getColor(R.color.gpsenabled)); // Mock provider hack if (application.gpsContinous) { map.setMoving(true); map.setFixed(true); getActivity().supportInvalidateOptionsMenu(); } } switch (application.gpsStatus) { case LocationService.GPS_OK: sattelites.setTextColor(getResources().getColor(R.color.gpsworking)); sattelites.setText( String.valueOf(application.gpsFSats) + "/" + String.valueOf(application.gpsTSats)); if (!map.isFixed()) { map.setMoving(true); map.setFixed(true); getActivity().supportInvalidateOptionsMenu(); } break; case LocationService.GPS_SEARCHING: sattelites.setTextColor(getResources().getColor(R.color.gpsenabled)); sattelites.setText( String.valueOf(application.gpsFSats) + "/" + String.valueOf(application.gpsTSats)); if (map.isFixed()) { map.setFixed(false); getActivity().supportInvalidateOptionsMenu(); } break; } } else { sattelites.setText(R.string.sat_stop); sattelites.setTextColor(getResources().getColor(R.color.gpsdisabled)); if (map.isMoving()) { map.setMoving(false); map.setFixed(false); getActivity().supportInvalidateOptionsMenu(); } } updatePanels(); if (application.lastKnownLocation == null) return; map.setLocation(application.lastKnownLocation); double track = application.fixDeclination(application.lastKnownLocation.getBearing()); speedValue.setText(StringFormatter.speedC(application.lastKnownLocation.getSpeed())); trackValue.setText(StringFormatter.angleC(track)); elevationValue.setText(StringFormatter.elevationC(application.lastKnownLocation.getAltitude())); // TODO set separate color if (application.gpsGeoid != lastGeoid) { int color = application.gpsGeoid ? 0xffffffff : getResources().getColor(R.color.gpsenabled); elevationValue.setTextColor(color); elevationUnit.setTextColor(color); ((TextView) getView().findViewById(R.id.elevationname)).setTextColor(color); lastGeoid = application.gpsGeoid; } if (application.shouldEnableFollowing && map.isFixed()) { application.shouldEnableFollowing = false; if (followOnLocation) setFollowing(true); } updateGPSStatus(); if (autoDim && dimInterval > 0 && application.lastKnownLocation.getTime() - lastDim >= dimInterval) { dimScreen(); lastDim = application.lastKnownLocation.getTime(); } } }; private void updateMapViewArea() { final ViewTreeObserver vto = map.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @SuppressLint("NewApi") @SuppressWarnings("deprecation") public void onGlobalLayout() { View root = getView(); Rect area = new Rect(); map.getLocalVisibleRect(area); View v = root.findViewById(R.id.topbar); if (v != null) area.top = v.getBottom(); v = root.findViewById(R.id.bottombar); if (v != null) area.bottom = v.getTop(); v = root.findViewById(R.id.rightbar); if (v != null) area.right = v.getLeft(); if (mapButtons.isShown()) { // Landscape mode if (v != null) area.bottom = mapButtons.getTop(); else area.right = mapButtons.getLeft(); } if (!area.isEmpty()) map.updateViewArea(area); ViewTreeObserver ob; if (vto.isAlive()) ob = vto; else ob = map.getViewTreeObserver(); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { ob.removeGlobalOnLayoutListener(this); } else { ob.removeOnGlobalLayoutListener(this); } } }); } private void onUpdateNavigationState() { boolean isNavigating = application.navigationService != null && application.navigationService.isNavigating(); boolean isNavigatingViaRoute = isNavigating && application.navigationService.isNavigatingViaRoute(); View rootView = getView(); // waypoint panel rootView.findViewById(R.id.waypointinfo).setVisibility(isNavigating ? View.VISIBLE : View.GONE); // route panel rootView.findViewById(R.id.routeinfo).setVisibility(isNavigatingViaRoute ? View.VISIBLE : View.GONE); // distance distanceValue.setVisibility(isNavigating ? View.VISIBLE : View.GONE); rootView.findViewById(R.id.distancelt).setVisibility(isNavigating ? View.VISIBLE : View.GONE); // bearing bearingValue.setVisibility(isNavigating ? View.VISIBLE : View.GONE); rootView.findViewById(R.id.bearinglt).setVisibility(isNavigating ? View.VISIBLE : View.GONE); // turn turnValue.setVisibility(isNavigating ? View.VISIBLE : View.GONE); rootView.findViewById(R.id.turnlt).setVisibility(isNavigating ? View.VISIBLE : View.GONE); // xtk xtkValue.setVisibility(isNavigatingViaRoute ? View.VISIBLE : View.GONE); rootView.findViewById(R.id.xtklt).setVisibility(isNavigatingViaRoute ? View.VISIBLE : View.GONE); // we hide elevation in portrait mode due to lack of space if (getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) { if (isNavigatingViaRoute && elevationValue.getVisibility() == View.VISIBLE) { elevationValue.setVisibility(View.GONE); rootView.findViewById(R.id.elevationlt).setVisibility(View.GONE); ViewGroup row = (ViewGroup) rootView.findViewById(R.id.movingrow); int pos = row.indexOfChild(elevationValue); View xtklt = rootView.findViewById(R.id.xtklt); row.removeView(xtkValue); row.removeView(xtklt); row.addView(xtklt, pos); row.addView(xtkValue, pos); row.getParent().requestLayout(); } else if (!isNavigatingViaRoute && elevationValue.getVisibility() == View.GONE) { elevationValue.setVisibility(View.VISIBLE); rootView.findViewById(R.id.elevationlt).setVisibility(View.VISIBLE); ViewGroup row = (ViewGroup) rootView.findViewById(R.id.movingrow); int pos = row.indexOfChild(xtkValue); View elevationlt = rootView.findViewById(R.id.elevationlt); row.removeView(elevationValue); row.removeView(elevationlt); row.addView(elevationlt, pos); row.addView(elevationValue, pos); row.getParent().requestLayout(); } } if (isNavigatingViaRoute) { routeName.setText("\u21d2 " + application.navigationService.navRoute.name); } if (isNavigating) { waypointName.setText("\u2192 " + application.navigationService.navWaypoint.name); } updateMapViewArea(); map.refreshMap(); } private void onUpdateNavigationStatus() { if (!application.isNavigating()) return; long now = System.currentTimeMillis(); double distance = application.navigationService.navDistance; double bearing = application.navigationService.navBearing; long turn = application.navigationService.navTurn; double vmg = application.navigationService.navVMG; int ete = application.navigationService.navETE; String[] dist = StringFormatter.distanceC(distance, StringFormatter.precisionFormat); String eteString = (ete == Integer.MAX_VALUE) ? getString(R.string.never) : (String) DateUtils.getRelativeTimeSpanString(now + (ete + 1) * 60000, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE); String extra = StringFormatter.speedH(vmg) + " | " + eteString; String trnsym = ""; if (turn > 0) { trnsym = "R"; } else if (turn < 0) { trnsym = "L"; turn = -turn; } bearing = application.fixDeclination(bearing); distanceValue.setText(dist[0]); distanceUnit.setText(dist[1]); bearingValue.setText(StringFormatter.angleC(bearing)); turnValue.setText(StringFormatter.angleC(turn) + trnsym); waypointExtra.setText(extra); if (application.navigationService.isNavigatingViaRoute()) { View rootView = getView(); boolean hasNext = application.navigationService.hasNextRouteWaypoint(); if (distance < application.navigationService.navProximity * 3 && !animationSet) { AnimationSet animation = new AnimationSet(true); animation.addAnimation(new AlphaAnimation(1.0f, 0.3f)); animation.addAnimation(new AlphaAnimation(0.3f, 1.0f)); animation.setDuration(500); animation.setRepeatCount(10); rootView.findViewById(R.id.waypointinfo).startAnimation(animation); if (!hasNext) { rootView.findViewById(R.id.routeinfo).startAnimation(animation); } animationSet = true; } else if (animationSet) { rootView.findViewById(R.id.waypointinfo).setAnimation(null); if (!hasNext) { rootView.findViewById(R.id.routeinfo).setAnimation(null); } animationSet = false; } if (application.navigationService.navXTK == Double.NEGATIVE_INFINITY) { xtkValue.setText("--"); xtkUnit.setText("--"); } else { String xtksym = application.navigationService.navXTK == 0 ? "" : application.navigationService.navXTK > 0 ? "R" : "L"; String[] xtks = StringFormatter.distanceC(Math.abs(application.navigationService.navXTK)); xtkValue.setText(xtks[0] + xtksym); xtkUnit.setText(xtks[1]); } double navDistance = application.navigationService.navRouteDistanceLeft(); int eta = application.navigationService.navRouteETE(navDistance); if (eta < Integer.MAX_VALUE) eta += application.navigationService.navETE; String etaString = (eta == Integer.MAX_VALUE) ? getString(R.string.never) : (String) DateUtils.getRelativeTimeSpanString(now + (eta + 1) * 60000, now, DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE); extra = StringFormatter.distanceH(navDistance + distance, 1000) + " | " + etaString; routeExtra.setText(extra); } } @Override public void mapTapped() { updatePanels(); } @Override public boolean waypointTapped(Waypoint waypoint, int x, int y) { try { if (application.editingRoute != null) { waypointSelected = application.getWaypointIndex(waypoint); showPopupMenu(R.menu.routeeditwaypoint_menu, x, y); return true; } else { waypointActionsCallback.onWaypointShow(waypoint); return true; } } catch (Exception e) { return false; } } @Override public boolean routeWaypointTapped(Route route, int index, int x, int y) { if (route == application.editingRoute) { routeActionsCallback.onRouteWaypointEdit(route, route.getWaypoint(index)); } else if (application.isNavigatingViaRoute() && application.navigationService.navRoute == route) { waypointSelected = index; showPopupMenu(R.menu.routewaypointnavigationonmap_menu, x, y); } else { routeActionsCallback.onRouteDetails(route); } return true; } @Override public boolean mapObjectTapped(long id, int x, int y) { mapObjectSelected = id; showPopupMenu(R.menu.mapobject_menu, x, y); return false; } @Override public void setFollowing(boolean follow) { if (follow && !map.isFixed()) return; followOnLocation = false; if (application.editingRoute == null && application.editingTrack == null) { if (showDistance > 0 && application.overlayManager.distanceOverlay != null) { if (showDistance == 2 && !follow) { application.overlayManager.distanceOverlay.setAncor(application.getLocation()); application.overlayManager.distanceOverlay.setEnabled(true); } else { application.overlayManager.distanceOverlay.setEnabled(false); } } following = follow; if (map != null) map.setFollowing(following); } getActivity().supportInvalidateOptionsMenu(); } @Override public void zoomMap(final float factor) { wait(new Waitable() { @Override public void waitFor() { synchronized (map) { if (application.zoomBy(factor)) conditionsChanged(); } } }); } @Override public void onTileObtained() { refreshMap(); } @Override public void refreshMap() { if (map == null) return; map.refreshMap(); } @Override public void conditionsChanged() { if (map == null) return; map.updateMapInfo(); map.updateMapCenter(); } @Override public void mapChanged() { if (map == null) return; map.suspendBestMap(); map.updateMapInfo(); map.updateMapCenter(); } private void updatePanels() { long now = SystemClock.uptimeMillis(); boolean changed = false; if ((now < map.lastDragTime + mapInfoHideDelay)) { if (mapInfo.getVisibility() != View.VISIBLE) { mapInfo.setVisibility(View.VISIBLE); if (mapButtonsVisible) mapButtons.setVisibility(View.VISIBLE); changed = true; } } else { if (mapInfo.getVisibility() != View.GONE) { mapInfo.setVisibility(View.GONE); mapButtons.setVisibility(View.GONE); changed = true; } } if ((now < map.lastDragTime + satInfoHideDelay)) { if (satInfo.getVisibility() != View.VISIBLE) { satInfo.setVisibility(View.VISIBLE); changed = true; } } else { if (satInfo.getVisibility() != View.GONE) { satInfo.setVisibility(View.GONE); changed = true; } } if ((now < map.lastDragTime + navInfoHideDelay)) { if (navInfo.getVisibility() != View.VISIBLE) { navInfo.setVisibility(View.VISIBLE); changed = true; } } else { if (navInfo.getVisibility() != View.GONE) { navInfo.setVisibility(View.GONE); changed = true; } } if (changed) updateMapViewArea(); } private void updateEditStatus() { if (application.editingRoute != null) { getView().findViewById(R.id.editroute).setVisibility(View.VISIBLE); //if (showDistance > 0) // application.overlayManager.distanceOverlay.setEnabled(false); setFollowing(false); } } private void updateGPSStatus() { int v = map.isMoving() && application.editingRoute == null && application.editingTrack == null ? View.VISIBLE : View.GONE; View view = getView().findViewById(R.id.movinginfo); if (view.getVisibility() != v) { view.setVisibility(v); updateMapViewArea(); } } @Override public void updateCoordinates(double[] latlon) { // TODO strange situation, needs investigation if (application != null) { final String pos = StringFormatter.coordinates(" ", latlon[0], latlon[1]); getActivity().runOnUiThread(new Runnable() { @Override public void run() { coordinates.setText(pos); } }); } } @Override public void updateFileInfo() { final String title = application.getMapTitle(); getActivity().runOnUiThread(new Runnable() { @Override public void run() { if (title != null) { currentFile.setText(title); } else { currentFile.setText("-no map-"); } double zoom = application.getZoom() * 100; if (zoom == 0.0) { mapZoom.setText("---%"); } else { int rz = (int) Math.floor(zoom); String zoomStr = zoom - rz != 0.0 ? String.format(Locale.getDefault(), "%.1f", zoom) : String.valueOf(rz); mapZoom.setText(zoomStr + "%"); } // ImageButton zoomin = (ImageButton) findViewById(R.id.zoomin); // ImageButton zoomout = (ImageButton) findViewById(R.id.zoomout); // zoomin.setEnabled(application.getNextZoom() != 0.0); // zoomout.setEnabled(application.getPrevZoom() != 0.0); // LightingColorFilter disable = new LightingColorFilter(0xFFFFFFFF, 0xFF444444); // zoomin.setColorFilter(zoomin.isEnabled() ? null : disable); // zoomout.setColorFilter(zoomout.isEnabled() ? null : disable); } }); } private void dimScreen() { int color = Color.TRANSPARENT; if (autoDim && application.lastKnownLocation != null) { Calendar now = GregorianCalendar.getInstance(TimeZone.getDefault()); if (!Astro.isDaytime(application.getZenith(), application.lastKnownLocation, now)) color = dimValue << 57; // value * 2 and shifted to transparency octet } dimView.setBackgroundColor(color); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Resources resources = getResources(); if (getString(R.string.pref_wakelock).equals(key)) { keepScreenOn = sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_wakelock)); map.setKeepScreenOn(keepScreenOn); } else if (getString(R.string.pref_showdistance_int).equals(key)) { showDistance = Integer.parseInt(sharedPreferences.getString(key, getString(R.string.def_showdistance))); } else if (getString(R.string.pref_hidemapinfo).equals(key)) { mapInfoHideDelay = Integer.parseInt(sharedPreferences.getString(key, "2147483647")); } else if (getString(R.string.pref_hidesatinfo).equals(key)) { satInfoHideDelay = Integer.parseInt(sharedPreferences.getString(key, "2147483647")); } else if (getString(R.string.pref_hidenavinfo).equals(key)) { navInfoHideDelay = Integer.parseInt(sharedPreferences.getString(key, "2147483647")); } else if (getString(R.string.pref_maprenderinterval).equals(key)) { updatePeriod = sharedPreferences.getInt(key, resources.getInteger(R.integer.def_maprenderinterval)) * 100; } else if (getString(R.string.pref_mapfollowonloc).equals(key)) { followOnLocation = sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_mapfollowonloc)); } else if (getString(R.string.pref_mapdiminterval).equals(key)) { dimInterval = sharedPreferences.getInt(key, resources.getInteger(R.integer.def_mapdiminterval)) * 1000; } else if (getString(R.string.pref_mapdimvalue).equals(key)) { dimValue = sharedPreferences.getInt(key, resources.getInteger(R.integer.def_mapdimvalue)); } else if (getString(R.string.pref_mapdim).equals(key)) { autoDim = sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_mapdim)); dimScreen(); } else if (getString(R.string.pref_unfollowontap).equals(key)) { map.setStrictUnfollow( !sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_unfollowontap))); } else if (getString(R.string.pref_lookahead).equals(key)) { map.setLookAhead(sharedPreferences.getInt(key, resources.getInteger(R.integer.def_lookahead))); } else if (getString(R.string.pref_mapbest).equals(key)) { map.setBestMapEnabled(sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_mapbest))); } else if (getString(R.string.pref_mapbestinterval).equals(key)) { map.setBestMapInterval( sharedPreferences.getInt(key, resources.getInteger(R.integer.def_mapbestinterval)) * 1000); } else if (getString(R.string.pref_scalebarbg).equals(key)) { map.setDrawScaleBarBackground( sharedPreferences.getBoolean(key, resources.getBoolean(R.bool.def_scalebarbg))); } else if (getString(R.string.pref_scalebarcolor).equals(key)) { map.setScaleBarColor(sharedPreferences.getInt(key, resources.getColor(R.color.scalebar))); } else if (getString(R.string.pref_scalebarbgcolor).equals(key)) { map.setScaleBarBackgroundColor(sharedPreferences.getInt(key, resources.getColor(R.color.scalebarbg))); } else if (getString(R.string.pref_hidemapcross).equals(key)) { int delay = Integer.parseInt(sharedPreferences.getString(key, "5")); map.setCrossCursorHideDelay(delay); } else if (getString(R.string.pref_mapcrosscolor).equals(key)) { map.setCrossColor(sharedPreferences.getInt(key, resources.getColor(R.color.mapcross))); } else if (getString(R.string.pref_cursorvector).equals(key) || getString(R.string.pref_cursorvectormlpr).equals(key)) { map.setCursorVector( Integer.parseInt(sharedPreferences.getString(getString(R.string.pref_cursorvector), getString(R.string.def_cursorvector))), sharedPreferences.getInt(getString(R.string.pref_cursorvectormlpr), resources.getInteger(R.integer.def_cursorvectormlpr))); } else if (getString(R.string.pref_cursorcolor).equals(key)) { map.setCursorColor(sharedPreferences.getInt(key, resources.getColor(R.color.cursor))); } else if (getString(R.string.pref_navigation_proximity).equals(key)) { map.setProximity(Integer .parseInt(sharedPreferences.getString(key, getString(R.string.def_navigation_proximity)))); } else if (getString(R.string.pref_unitspeed).equals(key)) { speedUnit.setText(StringFormatter.speedAbbr); } else if (getString(R.string.pref_unitelevation).equals(key)) { elevationUnit.setText(StringFormatter.elevationAbbr); } else if (getString(R.string.pref_unitangle).equals(key)) { trackUnit.setText(StringFormatter.angleAbbr); bearingUnit.setText(StringFormatter.angleAbbr); turnUnit.setText(StringFormatter.angleAbbr); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.zoomin: // TODO Show toast here if (application.getNextZoom() == 0.0) return; wait(new Waitable() { @Override public void waitFor() { synchronized (map) { if (application.zoomIn()) conditionsChanged(); } } }); break; case R.id.zoomout: if (application.getPrevZoom() == 0.0) return; wait(new Waitable() { @Override public void waitFor() { synchronized (map) { if (application.zoomOut()) conditionsChanged(); } } }); break; case R.id.prevmap: wait(new Waitable() { @Override public void waitFor() { synchronized (map) { if (application.prevMap()) mapChanged(); } } }); break; case R.id.nextmap: wait(new Waitable() { @Override public void waitFor() { synchronized (map) { if (application.nextMap()) mapChanged(); } } }); break; case R.id.coordinates: { showPopupMenu(R.menu.location_menu, v); break; } case R.id.sats: { if (application.gpsEnabled) { GPSInfo dialog = new GPSInfo(); dialog.show(getFragmentManager(), "dialog"); } break; } case R.id.currentfile: { SuitableMapsList dialog = new SuitableMapsList(); dialog.show(getFragmentManager(), "dialog"); break; } case R.id.currentzoom: { mapButtonsVisible = !mapButtonsVisible; mapButtons.setVisibility(mapButtonsVisible ? View.VISIBLE : View.GONE); updateMapViewArea(); // save panel state Editor editor = PreferenceManager.getDefaultSharedPreferences(application).edit(); editor.putBoolean(getString(R.string.ui_mapbuttons_shown), mapButtonsVisible); editor.commit(); break; } case R.id.waypointname: { if (application.isNavigatingViaRoute()) { routeActionsCallback.onRouteDetails(application.navigationService.navRoute); } break; } case R.id.addpoint: { double[] aloc = application.getMapCenter(); application.routeEditingWaypoints.push(application.editingRoute .addWaypoint("RWPT" + application.editingRoute.length(), aloc[0], aloc[1])); refreshMap(); break; } case R.id.insertpoint: { double[] iloc = application.getMapCenter(); application.routeEditingWaypoints.push(application.editingRoute .insertWaypoint("RWPT" + application.editingRoute.length(), iloc[0], iloc[1])); refreshMap(); break; } case R.id.removepoint: { if (!application.routeEditingWaypoints.empty()) { application.editingRoute.removeWaypoint(application.routeEditingWaypoints.pop()); refreshMap(); } break; } case R.id.orderpoints: { RouteEdit dialog = new RouteEdit(application.editingRoute); dialog.show(getFragmentManager(), "dialog"); break; } case R.id.finishrouteedit: { if ("New route".equals(application.editingRoute.name)) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()); application.editingRoute.name = formatter.format(new Date()); } application.editingRoute.editing = false; application.dispatchRoutePropertiesChanged(application.editingRoute); application.editingRoute = null; application.routeEditingWaypoints = null; getView().findViewById(R.id.editroute).setVisibility(View.GONE); updateGPSStatus(); if (showDistance == 2) { application.overlayManager.distanceOverlay.setEnabled(true); } updateMapViewArea(); refreshMap(); map.requestFocus(); break; } } } private final Handler zoomHandler = new Handler(); @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: zoom100X = (int) event.getRawX(); zoom100Y = (int) event.getRawY(); zoomHandler.postDelayed(new Runnable() { @Override public void run() { zoom100X = 0; zoom100Y = 0; } }, 2000); break; case MotionEvent.ACTION_UP: int dx = Math.abs((int) event.getRawX() - zoom100X); int dy = (int) event.getRawY() - zoom100Y; int h = v.getHeight(); int w = v.getWidth() >> 1; if (dy > h * 0.8 && dy < h * 2 && dx < w) { wait(new Waitable() { @Override public void waitFor() { synchronized (map) { if (application.setZoom(1.)) { map.updateMapInfo(); map.updateMapCenter(); } } } }); zoom100X = 0; zoom100Y = 0; } break; } return false; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_add_waypoint: double[] loc = application.getMapCenter(); Waypoint waypoint = new Waypoint("", "", loc[0], loc[1]); waypoint.date = Calendar.getInstance().getTime(); int wpt = application.addWaypoint(waypoint); waypoint.name = "WPT" + wpt; application.saveDefaultWaypoints(); refreshMap(); return true; case R.id.action_follow: setFollowing(!following); return true; case R.id.action_next_nav_point: application.navigationService.nextRouteWaypoint(); return true; case R.id.action_prev_nav_point: application.navigationService.prevRouteWaypoint(); return true; case R.id.action_stop_navigation: application.stopNavigation(); return true; case R.id.action_search: getActivity().onSearchRequested(); return true; case R.id.action_locate: { final Location l = application.getLastKnownSystemLocation(); final long now = System.currentTimeMillis(); final long fixed = l != null ? l.getTime() : 0L; if ((now - fixed) < 1000 * 60 * 60 * 12) // we do not take into account locations older then 12 hours { wait(new Waitable() { @Override public void waitFor() { if (application.ensureVisible(l.getLatitude(), l.getLongitude())) mapChanged(); else conditionsChanged(); getActivity().runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(application, DateUtils.getRelativeTimeSpanString(fixed, now, DateUtils.SECOND_IN_MILLIS), Toast.LENGTH_SHORT).show(); } }); } }); } else { Toast.makeText(application, getString(R.string.msg_nolastknownlocation), Toast.LENGTH_LONG).show(); } return true; } case R.id.action_locating: application.enableLocating(!application.isLocating()); return true; case R.id.action_tracking: application.enableTracking(!application.isTracking()); return true; default: return super.onOptionsItemSelected(item); } } @Override public boolean onMenuItemSelected(MenuBuilder builder, MenuItem item) { switch (item.getItemId()) { case R.id.action_information: { FragmentManager manager = getFragmentManager(); LocationInfo dialog = new LocationInfo(application.getMapCenter()); dialog.show(manager, "dialog"); return true; } case R.id.action_share: { Intent i = new Intent(android.content.Intent.ACTION_SEND); i.setType("text/plain"); i.putExtra(Intent.EXTRA_SUBJECT, R.string.currentloc); double[] loc = application.getMapCenter(); String spos = StringFormatter.coordinates(" ", loc[0], loc[1]); i.putExtra(Intent.EXTRA_TEXT, spos); startActivity(Intent.createChooser(i, getString(R.string.menu_share))); return true; } case R.id.action_view_elsewhere: { double[] sloc = application.getMapCenter(); String geoUri = "geo:" + Double.toString(sloc[0]) + "," + Double.toString(sloc[1]); Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(geoUri)); startActivity(intent); return true; } case R.id.action_copy_location: { double[] cloc = application.getMapCenter(); String cpos = StringFormatter.coordinates(" ", cloc[0], cloc[1]); Clipboard.copy(getActivity(), cpos); return true; } case R.id.action_paste_location: { String text = Clipboard.paste(getActivity()); try { double c[] = CoordinateParser.parse(text); if (!Double.isNaN(c[0]) && !Double.isNaN(c[1])) { boolean mapChanged = application.setMapCenter(c[0], c[1], true, true, false); if (mapChanged) map.updateMapInfo(); map.updateMapCenter(); following = false; map.setFollowing(false); } } catch (IllegalArgumentException e) { } return true; } case R.id.action_add_to_route: { Waypoint wpt = application.getWaypoint(waypointSelected); application.routeEditingWaypoints .push(application.editingRoute.addWaypoint(wpt.name, wpt.latitude, wpt.longitude)); refreshMap(); return true; } case R.id.action_navigate: { application.navigationService.setRouteWaypoint(waypointSelected); return true; } case R.id.action_mapobject_navigate: { application.startNavigation(application.getMapObject(mapObjectSelected)); return true; } } return false; } @Override public void onMenuModeChange(MenuBuilder builder) { } @Override public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { anchor.setVisibility(View.GONE); waypointSelected = -1; mapObjectSelected = -1; } @Override public boolean onOpenSubMenu(MenuBuilder menu) { return false; } @SuppressLint("NewApi") private void showPopupMenu(int resId, int x, int y) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { anchor.setX(x); anchor.setY(y); } else { //TODO Test it! RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); params.leftMargin = x; params.topMargin = y; anchor.setLayoutParams(params); } anchor.setVisibility(View.VISIBLE); showPopupMenu(resId, anchor); } private void showPopupMenu(int resId, View v) { // https://gist.github.com/mediavrog/9345938#file-iconizedmenu-java-L55 MenuBuilder menu; MenuPopupHelper popup; Activity activity = getActivity(); menu = new MenuBuilder(activity); menu.setCallback(this); popup = new MenuPopupHelper(activity, menu, v); popup.setCallback(this); popup.setForceShowIcon(true); new SupportMenuInflater(activity).inflate(resId, menu); popup.show(); } private void wait(final Waitable w) { waitBar.setVisibility(View.VISIBLE); waitBar.setText(R.string.msg_wait); executorThread.execute(new Runnable() { public void run() { w.waitFor(); finishHandler.sendEmptyMessage(0); } }); } private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(NavigationService.BROADCAST_NAVIGATION_STATE)) { onUpdateNavigationState(); getActivity().supportInvalidateOptionsMenu(); } else if (action.equals(NavigationService.BROADCAST_NAVIGATION_STATUS)) { onUpdateNavigationStatus(); getActivity().supportInvalidateOptionsMenu(); } else if (action.equals(LocationService.BROADCAST_LOCATING_STATUS)) { if (!application.isLocating()) map.clearLocation(); } // In fact this is not needed on modern devices through activity is always // paused when the screen is turned off. But we will keep it, may be there // exist some devices (ROMs) that do not pause activities. else if (action.equals(Intent.ACTION_SCREEN_OFF)) { map.pause(); } else if (action.equals(Intent.ACTION_SCREEN_ON)) { map.resume(); } } }; @SuppressLint("HandlerLeak") private class FinishHandler extends Handler { private final WeakReference<MapFragment> target; FinishHandler(MapFragment fragment) { this.target = new WeakReference<MapFragment>(fragment); } @Override public void handleMessage(Message msg) { MapFragment mapFragment = target.get(); if (mapFragment != null) { mapFragment.waitBar.setVisibility(View.INVISIBLE); mapFragment.waitBar.setText(""); } } } private interface Waitable { public void waitFor(); } }