public class Fawlty extends Base {
	private static final String STATE_ENABLED = "fawlty_enabled";
	protected boolean enabled;
	protected WirelessEnvListener wirelessEnvListener;
	protected String last_ident;
	protected LatLong last_latLong;
	protected LatLong last_latLong_tower;
	protected Circle circle;
	protected Marker marker;
	protected Bitmap bitmap;

	static double distance_in_meter(final LatLong latlong1, final LatLong latlong2) {
		final double R = 6371000f; // Radius of the earth in m
		final double dLat = (latlong1.latitude - latlong2.latitude) * Math.PI / 180f;
		final double dLon = (latlong1.longitude - latlong2.longitude) * Math.PI / 180f;
		final double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
				Math.cos(latlong1.latitude * Math.PI / 180f) * Math.cos(latlong2.latitude * Math.PI / 180f) *
						Math.sin(dLon / 2) * Math.sin(dLon / 2);
		final double c = 2f * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
		return R * c;
	}

	@Override public void onCreate(Bundle savedInstanceState) {
		//if (DEBUG) Log.d(TAG, "Fawlty.onCreate");
		super.onCreate(savedInstanceState);
		if (savedInstanceState != null) {
			enabled = savedInstanceState.getBoolean(STATE_ENABLED);
		}
		last_latLong = new LatLong(0, 0);
		last_latLong_tower = null;
		wirelessEnvListener = new WirelessEnvListener(getActivity()) {
			@Override public void onLocationChanged(Location location, String ident) {
				//if (DEBUG) Log.d(TAG, "got it location=" + location + ", ident=" + ident + ", last_ident=" + Fawlty.this.last_ident);
				last_latLong = new LatLong(location.getLatitude(), location.getLongitude());
				if (location.getExtras().containsKey("latitude_tower")) {
					last_latLong_tower = new LatLong(location.getExtras().getDouble("latitude_tower"), location.getExtras().getDouble("longitude_tower"));
				} else {
					last_latLong_tower = null;
				}
				float accuracy = location.getAccuracy();
				long rcd = location.getExtras().getLong("rcd", 5000);
				if (!ident.equals(Fawlty.this.last_ident) || !last_latLong.equals(circle.getPosition())) {
					circle.setLatLong(last_latLong);
					if (rcd == 2000) {
						circle.setRadius(accuracy);
						if (last_latLong_tower != null) {
							marker.setLatLong(last_latLong_tower);
							marker.setVisible(true);
						} else {
							marker.setVisible(false);
						}
						circle.setVisible(true);
					} else {
						circle.setVisible(false);
						marker.setVisible(false);
						getActivity().runOnUiThread(new Runnable() {
							public void run() {
								Toast.makeText(getActivity(), "Ident: " + last_ident + " not found", Toast.LENGTH_LONG).show();
							}
						});
					}
					Fawlty.this.last_ident = ident;
					//if (DEBUG) Log.d(TAG, "location set");
					notice(R.id.event_notify_cell, "cell_ident", ident);
				}
			}
		};
	}

	@Override public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		if (DEBUG) Log.d(TAG, "Fawlty.onSaveInstanceState");
		outState.putBoolean(STATE_ENABLED, enabled);
	}

	@Override public void onResume() {
		super.onResume();
		if (DEBUG) Log.d(TAG, "Fawlty.onResume");
		if (enabled) {
			enable();
		}
	}

	@Override public void onPause() {
		super.onPause();
		if (DEBUG) Log.d(TAG, "Fawlty.onPause");
		disable();
	}

	void enable() {
		MapView mapView = ((Tabulae) getActivity()).getMapView();
		if (circle == null) {
			Paint paint = AndroidGraphicFactory.INSTANCE.createPaint();
			paint.setColor(0x77ff0000);
			paint.setStrokeWidth(0);
			paint.setStyle(Style.FILL);
			circle = new Circle(last_latLong, 1, paint, null) {
				@Override public boolean onTap(LatLong geoPoint, Point viewPosition, Point tapPoint) {
					if (contains(geoPoint)) {
						Toast.makeText(getActivity(), "Ident: " + last_ident, Toast.LENGTH_LONG).show();
						return true;
					}
					return false;
				}

				boolean contains(LatLong geoPoint) {
					double d = distance_in_meter(getPosition(), geoPoint);
					if (DEBUG) Log.d(TAG, "contains d=" + d + ", radius=" + getRadius());
					return d < getRadius();
				}
			};
			bitmap = AndroidGraphicFactory.convertToBitmap(
					getResources().getDrawable(R.drawable.poi_red, null));
			bitmap.incrementRefCount();
			marker = new Marker(new LatLong(0, 0), bitmap, 0, -bitmap.getHeight() / 2);
			marker.setVisible(false);
		}
		mapView.getLayerManager().getLayers().add(circle);
		mapView.getLayerManager().getLayers().add(marker);
		//paint = (Paint)paint.clone();
		//paint.setColor(0x7700ff00);
		//FixedPixelCircle tappableCircle = new FixedPixelCircle(new LatLong(51.24,6.79), 50, paint, null);
		//mapView.getLayerManager().getLayers().add(tappableCircle);
		wirelessEnvListener.enable();
	}

	void disable() {
		wirelessEnvListener.disable();
		if (circle != null) {
			MapView mapView = ((Tabulae) getActivity()).getMapView();
			if (mapView != null && mapView.getLayerManager() != null) {
				mapView.getLayerManager().getLayers().remove(circle);
				//circle.onDestroy();
				//circle = null;
				//bitmap.decrementRefCount();
				mapView.getLayerManager().getLayers().remove(marker);
				//marker.onDestroy();
				//marker = null;
			}
		}
	}

	public void inform(int event, Bundle extra) {
		switch (event) {
			case R.id.event_request_fawlty: {
				notice(R.id.event_notify_fawlty, "enabled", enabled);
			}
			break;
			case R.id.event_do_fawlty: {
				if (enabled) {
					disable();
					enabled = false;
					Toast.makeText(getActivity(), "Serving cell disabled", Toast.LENGTH_SHORT).show();
				} else {
					enable();
					enabled = true;
					Toast.makeText(getActivity(), "Serving cell enabled", Toast.LENGTH_SHORT).show();
				}
				notice(R.id.event_notify_fawlty, "enabled", enabled);
			}
			break;
		}
	}
} public class Controller extends Base {
	private Animation popOutAnimation;
	private Animation popInAnimation;
	private boolean optionsOut;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		if (de.emdete.tabulae.Constants.DEBUG) Log.d(Constants.TAG, "Controller.onCreateView");
		View view = inflater.inflate(R.layout.controller, container, false);
		View.OnClickListener clickListener = new View.OnClickListener() {
			@Override
			public void onClick(View view) {
				int e = view.getId();
				// if (DEBUG) Log.d(TAG, "Controller.onClick e=" + e);
				switch (e) {
					case R.id.event_do_attribute: {
						if (!optionsOut) {
							getActivity().findViewById(R.id.attributes).startAnimation(popOutAnimation);
							optionsOut = true;
							return;
						}
					}
					break;
				}
				((Tabulae) getActivity()).inform(e, null);
			}
		};
		for (int resourceId : new int[]{
				R.id.event_do_attribute_blue,
				R.id.event_do_attribute_green,
				R.id.event_do_attribute_red,
				R.id.event_do_attribute_white,
				R.id.event_do_attribute_yellow,
				R.id.event_do_attribute,
				R.id.event_do_autofollow,
				R.id.event_do_overlay,
				R.id.event_do_zoom_in,
				R.id.event_do_zoom_out,
		}) {
			view.findViewById(resourceId).setOnClickListener(clickListener);
		}
		return view;
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		if (Constants.DEBUG) Log.d(Constants.TAG, "Controller.onActivityCreated");
		popOutAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.attributes_open);
		popInAnimation = AnimationUtils.loadAnimation(getActivity(), R.anim.attributes_close);
		((Tabulae)getActivity()).inform(R.id.event_request_autofollow, null);
	}

	public void inform(int event, Bundle extra) {
		//if (DEBUG) Log.d(TAG, "Controller.inform event=" + event + ", extra=" + extra);
		switch (event) {
			case R.id.event_do_attribute_red: {
				((ImageButton) getActivity().findViewById(R.id.event_do_attribute)).setImageResource(R.drawable.attribute_red);
			}
			break;
			case R.id.event_do_attribute_yellow: {
				((ImageButton) getActivity().findViewById(R.id.event_do_attribute)).setImageResource(R.drawable.attribute_yellow);
			}
			break;
			case R.id.event_do_attribute_green: {
				((ImageButton) getActivity().findViewById(R.id.event_do_attribute)).setImageResource(R.drawable.attribute_green);
			}
			break;
			case R.id.event_do_attribute_blue: {
				((ImageButton) getActivity().findViewById(R.id.event_do_attribute)).setImageResource(R.drawable.attribute_blue);
			}
			break;
			case R.id.event_do_attribute_white: {
				((ImageButton) getActivity().findViewById(R.id.event_do_attribute)).setImageResource(R.drawable.attribute_white);
			}
			break;
			case R.id.event_do_attribute:
			case R.id.event_do_overlay:
			case R.id.event_do_zoom_in:
			case R.id.event_do_zoom_out:
			case R.id.event_do_autofollow:
				break;
			case R.id.event_notify_autofollow: {
				Activity activity = getActivity();
				if (activity != null) {
					activity.findViewById(R.id.event_do_autofollow).setVisibility(
						extra.getBoolean("autofollow")? View.INVISIBLE : View.VISIBLE);
				}
			}
			break;
			default: // prevent closing attributes on unknown events
				return;
		}
		if (optionsOut) {
			getActivity().findViewById(R.id.attributes).startAnimation(popInAnimation);
			optionsOut = false;
		}
	}
} public class Dashboard extends Base {
	protected DashboardItem[] dashboardItems;
	protected boolean enabled = true;

	@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		if (de.emdete.tabulae.Constants.DEBUG) Log.d(Constants.TAG, "Dashboard.onCreateView");
		return inflater.inflate(R.layout.dashboard, container, false);
	}

	@Override public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		if (Constants.DEBUG) Log.d(Constants.TAG, "Dashboard.onActivityCreated");
		Activity a = getActivity();
		ViewGroup viewGroup = (ViewGroup)a.findViewById(R.id.dashboard).findViewById(R.id.dashboard_list);
		viewGroup.removeAllViews();
		dashboardItems = new DashboardItem[]{
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_provider), "provider", getString(R.string.unit_empty)),
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_satellite), "satellites", getString(R.string.unit_empty)),
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_bearing), "bearing", getString(R.string.unit_degree)),
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_accuracy), "accuracy", getString(R.string.unit_m)),
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_speed), "speed", getString(R.string.unit_kmh)),
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_pace), "pace", getString(R.string.unit_minkm)),
				new DashboardItem(a, viewGroup, R.id.event_notify_zoom, getString(R.string.title_zoom), "zoom_level", getString(R.string.unit_zoom)),
				new DashboardItem(a, viewGroup, R.id.event_notify_cell, "Cell Identifier", "cell_ident", ""),
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_latitude), LATITUDE, getString(R.string.unit_degree)),
				new DashboardItem(a, viewGroup, R.id.event_notify_location, getString(R.string.title_longitude), LONGITUDE, getString(R.string.unit_degree)),
				new DashboardItem(a, viewGroup, R.id.event_notify_map, getString(R.string.title_current_map), "current_map", getString(R.string.unit_empty)),
				new DashboardItem(a, viewGroup, R.id.event_notify_time, getString(R.string.title_current_time), "current_time", getString(R.string.unit_empty)),
		};
		for (int e: new int[]{
			R.id.event_request_cell,
			R.id.event_request_location,
			R.id.event_request_map,
			R.id.event_request_time,
			R.id.event_request_zoom,
			}) {
			((Tabulae)a).inform(e, null);
		}
		notice(R.id.event_notify_dashboard, "enabled", enabled);
	}

	public void inform(int event, Bundle extra) {
		//if (DEBUG) Log.d(TAG, "Dashboard.inform event=" + event + ", extra=" + extra);
		if (dashboardItems != null) {
			for (DashboardItem d : dashboardItems) {
				d.inform(event, extra);
			}
		}
		switch (event) {
			case R.id.event_request_dashboard: {
				notice(R.id.event_notify_dashboard, "enabled", enabled);
			}
			break;
			case R.id.event_do_dashboard: {
				enabled = !enabled;
				getActivity().findViewById(R.id.dashboard_list).setVisibility(enabled ? View.VISIBLE : View.GONE);
				notice(R.id.event_notify_dashboard, "enabled", enabled);
			}
			break;
		}
	}

	static class DashboardItem {
		int event;
		String value_key;
		TextView textView;
		Activity activity;

		DashboardItem(Activity activity, ViewGroup viewGroup, int event, String header, String value_key, String unit) {
			View item = LayoutInflater.from(activity).inflate(R.layout.dashboard_item, viewGroup, false);
			((TextView) item.findViewById(R.id.data_header)).setText(header);
			((TextView) item.findViewById(R.id.data_unit)).setText(unit);
			viewGroup.addView(item);
			textView = ((TextView) item.findViewById(R.id.data_value));
			this.value_key = value_key;
			this.event = event;
			this.activity = activity;
			textView.setText("---");
		}

		void inform(int event, Bundle extra) {
			if (event == this.event && extra != null && extra.containsKey(value_key)) {
				Object temp = extra.get(value_key);
				if (temp == null) {
					temp = "---";
				}
				String value = temp.toString();
				if (value.length() > 8) {
					value = value.substring(0, 8);
				}
				final String v = value;
				activity.runOnUiThread(new Runnable() {
					public void run() {
						textView.setText(v);
					}
				});
			}
		}
	}
} public class DocumentAvtivity extends Activity {
	Context context;
	HtmlViewer browser;
	String url;

	@Override public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (DEBUG) Log.d(TAG, "onCreate intent=" + getIntent());
		url = getIntent().getExtras().getString("url");
		context = getBaseContext();
		browser = new HtmlViewer(context, null);
		browser.loadUrl(url);
		setContentView(browser);
	}

	@Override public void onBackPressed() {
		if (!browser.back()) {
			finish();
		}
	}

	class HtmlViewer extends WebView {
		public HtmlViewer(Context context, AttributeSet a) {
			super(context, a);
			getSettings().setLoadsImagesAutomatically(true);
			getSettings().setJavaScriptEnabled(false);
			getSettings().setAllowContentAccess(false);
			setWebViewClient(new WebViewClient (){
				@Override public boolean shouldOverrideUrlLoading(WebView view, String url) {
					if (DEBUG) Log.d(TAG, "shouldOverrideUrlLoading url=" + url);
					if (!url.startsWith("file:///")) {
						view.loadUrl(DocumentAvtivity.this.url);
						return true;
					}
					return false;
				}
				@Override public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) {
					if (DEBUG) Log.d(TAG, "shouldInterceptRequest request=" + request);
					return null;//super.shouldInterceptRequest(view, request);
				}
				@Override public WebResourceResponse shouldInterceptRequest (WebView view, String url) {
					if (DEBUG) Log.d(TAG, "shouldInterceptRequest url=" + url);
					return null;//super.shouldInterceptRequest(view, url);
				}
				@Override public void onLoadResource (WebView view, String url) {
					if (DEBUG) Log.d(TAG, "onLoadResource url=" + url);
					super.onLoadResource(view, url);
				}
			});
			setWebChromeClient(new WebChromeClient() {
			});
		}

		boolean back() {
			if (canGoBack()) {
				goBack();
				return true;
			}
			return false;
		}
	}
} public class Locus extends Base {
	Messenger mService = null;
	boolean mIsBound;
	ServiceConnection mConnection = new ServiceConnection() {
		public void onServiceConnected(ComponentName className, IBinder service) {
			mService = new Messenger(service);
			try {
				Message msg = Message.obtain(null, R.id.message_locus_register_client);
				msg.replyTo = mMessenger;
				mService.send(msg);
				msg = Message.obtain(null, R.id.message_locus_set_value, this.hashCode(), 0, new Bundle());
				mService.send(msg);
			}
			catch (RemoteException ignore) {
			}
			Log.d(TAG, "Sample.ServiceConnection.onServiceConnected: remote service connected");
		}

		public void onServiceDisconnected(ComponentName className) {
			mService = null;
			Log.d(TAG, "Sample.ServiceConnection.onServiceDisconnected: remote service disconnected");
		}
	};
	private ThreeStateLocationOverlay myLocationOverlay;
	final Messenger mMessenger = new Messenger(new Handler() {
		@Override public void handleMessage(Message msg) {
			switch (msg.what) {
				case R.id.message_locus_set_value:
					//if (DEBUG) Log.d(TAG, "Locus.handleMessage event=" + msg.arg1 + ", extra=" + msg.obj);
					if (getActivity() != null) {
						((Tabulae) getActivity()).inform(msg.arg1, (Bundle) msg.obj);
					}
					break;
				default:
					super.handleMessage(msg);
			}
		}
	});

	public static Location toLocation(Bundle location) {
		Location ret = null;
		if (location != null) {
			ret = new Location(location.getString("provider"));
			ret.setElapsedRealtimeNanos(location.getLong("elapsed"));
			ret.setLatitude(location.getDouble(LATITUDE));
			ret.setLongitude(location.getDouble(LONGITUDE));
			ret.setTime(location.getLong("time"));
			if (location.containsKey("speed")) {
				ret.setSpeed((float) (location.getDouble("speed") / 3.6));
			}
			if (location.containsKey("accuracy")) {
				ret.setAccuracy((float) location.getDouble("accuracy"));
			}
			if (location.containsKey("altitude")) {
				ret.setAltitude(location.getDouble("altitude"));
			}
			if (location.containsKey("bearing")) {
				ret.setBearing((float) location.getDouble("bearing"));
			}
		}
		return ret;
	}

	@Override
	public void onCreate(Bundle bundle) {
		super.onCreate(bundle);
		if (DEBUG) Log.d(TAG, "Locus.onCreate");
		MapView mapView = ((Tabulae) getActivity()).getMapView();
		myLocationOverlay = new ThreeStateLocationOverlay(getActivity());
	}

	@Override
	public void onResume() {
		super.onResume();
		if (DEBUG) Log.d(TAG, "Locus.onResume");
		MapView mapView = ((Tabulae) getActivity()).getMapView();
		mapView.getLayerManager().getLayers().add(myLocationOverlay);
		myLocationOverlay.enable();
		doBindService();
	}

	@Override
	public void onPause() {
		super.onPause();
		if (DEBUG) Log.d(TAG, "Locus.onPause");
		MapView mapView = ((Tabulae) getActivity()).getMapView();
		if (mapView.getLayerManager() != null) {
			mapView.getLayerManager().getLayers().remove(myLocationOverlay);
		}
		doUnbindService();
	}

	void doBindService() {
		getActivity().bindService(new Intent(getActivity(), LocusService.class), mConnection, Context.BIND_AUTO_CREATE);
		mIsBound = true;
	}

	void doUnbindService() {
		if (mIsBound) {
			mIsBound = false;
			if (mService != null) {
				try {
					Message msg = Message.obtain(null, R.id.message_locus_unregister_client);
					msg.replyTo = mMessenger;
					mService.send(msg);
				}
				catch (RemoteException ignore) {
				}
			}
			getActivity().unbindService(mConnection);
		}
	}

	public void inform(int event, Bundle extra) {
		//if (DEBUG) Log.d(TAG, "Locus.inform event=" + event + ", extra=" + extra);
		switch (event) {
			case R.id.event_notify_location: {
				if (DEBUG) Log.d(TAG, "Locus.inform event=" + event + ", extra=" + extra);
				if (myLocationOverlay != null) {
					myLocationOverlay.onLocationChanged(toLocation(extra));
				}
			}
			break;
		}
	}
} public class Poi extends Base {
	protected List<PointAd> pointsAd = new ArrayList<>();

	@Override
	public void onCreate(Bundle bundle) {
		if (DEBUG) Log.d(TAG, "Poi.onCreate");
		super.onCreate(bundle);
	}

	@Override
	public void onResume() {
		super.onResume();
		if (DEBUG) Log.d(TAG, "Poi.onResume");
		//MapView mapView = ((Tabulae)getActivity()).getMapView();
		try {
			try (final SQLiteDatabase db = ((Tabulae)getActivity()).getReadableDatabase()) {
				for (StoreObject item: StoreObject.query(db, PoiItem.class).where("visible").equal(true).fetchAll()) {
					PoiItem poiItem = (PoiItem)item;
					Log.d(TAG, "Poi.onResume poiItem=" + poiItem);
					pointsAd.add(new PointAd(poiItem));
				}
			}
		}
		catch (Exception e) {
			Log.e(TAG, "Poi.onResume e=" + e, e);
		}
	}

	@Override
	public void onPause() {
		super.onPause();
		if (DEBUG) Log.d(TAG, "Poi.onPause");
		for (PointAd pointAd : pointsAd) {
			pointAd.onDestroy();
		}
		pointsAd.clear();
	}

	class PointAd {
		protected PoiItem poiItem;
		protected Marker marker;
		protected Bitmap bitmap;

		PointAd(final PoiItem poiItem) {
			this.poiItem = poiItem;
			MapView mapView = ((Tabulae) getActivity()).getMapView();
			if (marker != null) {
				bitmap.decrementRefCount();
				mapView.getLayerManager().getLayers().remove(marker);
				marker.onDestroy();
				marker = null;
			}
			LatLong latLong = new LatLong(poiItem.getLatitude(), poiItem.getLongitude());
			bitmap = AndroidGraphicFactory.convertToBitmap(getResources().getDrawable(R.drawable.poi_black, null));
			bitmap.incrementRefCount();
			marker = new Marker(latLong, bitmap, 0, -bitmap.getHeight() / 2) {
				@Override
				public boolean onTap(LatLong geoPoint, Point viewPosition, Point tapPoint) {
					if (contains(viewPosition, tapPoint)) {
						Toast.makeText(getActivity(), String.format("%s: '%s'", poiItem.getName(), poiItem.getDescription()), Toast.LENGTH_SHORT).show();
						return true;
					}
					return false;
				}
			};
			mapView.getLayerManager().getLayers().add(marker);
		}

		void onDestroy() {
			if (DEBUG) Log.d(TAG, "Poi.PointAd.onDestroy");
			MapView mapView = ((Tabulae) getActivity()).getMapView();
			if (marker != null) {
				bitmap.decrementRefCount();
				mapView.getLayerManager().getLayers().remove(marker);
				marker.onDestroy();
				marker = null;
			}
			poiItem.setVisible(false);
			try {
				try (final SQLiteDatabase db = ((Tabulae)getActivity()).getWritableDatabase()) {
					poiItem.update(db);
				}
			}
			catch (Exception e) {
				Log.e(TAG, "Poi.PointAd.onDestroy e=" + e, e);
			}
		}
	}

	static public long storePointPosition(Tabulae activity, String name, String description, double latitude, double longitude, boolean visible) {
		//noinspection UnusedAssignment
		PoiItem poiItem = null;
		try {
			List<StoreObject> items = null;
			try (final SQLiteDatabase db = activity.getReadableDatabase()) {
				items = StoreObject.query(db, PoiItem.class).where("name").equal(name).fetchAll();
			}
			switch (items.size()) {
				case 0:
					if (DEBUG) Log.d(TAG, "Poi.storePointPosition new poiItem name=" + name);
					poiItem = new PoiItem(name, description, latitude, longitude, visible);
					break;
				case 1:
					if (DEBUG) Log.d(TAG, "Poi.storePointPosition update poiItem name=" + name);
					poiItem = (PoiItem)items.get(0);
					poiItem.setDescription(description);
					poiItem.setLatitude(latitude);
					poiItem.setLongitude(longitude);
					poiItem.setVisible(visible);
					break;
				default:
					Log.e(TAG, "Poi.storePointPosition poiItem not unique! name=" + name);
					throw new RuntimeException("Not unique by name");
			}
			try (final SQLiteDatabase db = activity.getWritableDatabase()) {
				return poiItem.insert(db).getId();
			}
		}
		catch (Exception e) {
			Log.e(TAG, "Poi.storePointPosition e=" + e, e);
		}
		return -1L;
	}

	void hideAll() {
		for (PointAd pointAd : pointsAd) {
			pointAd.onDestroy();
		}
		pointsAd.clear();
	}

	void center() {
		MapView mapView = ((Tabulae) getActivity()).getMapView();
		Bundle extra = new Bundle();
		extra.putBoolean("autofollow", false);
		((Tabulae) getActivity()).inform(R.id.event_do_autofollow, extra);
		LatLong latLong = mapView.getModel().mapViewPosition.getMapPosition().latLong;
		BoundingBox bb = new BoundingBox(latLong.latitude, latLong.longitude, latLong.latitude, latLong.longitude);
		for (PointAd pointAd : pointsAd) {
			PoiItem poiItem = pointAd.poiItem;
			latLong = new LatLong(poiItem.getLatitude(), poiItem.getLongitude());
			bb = bb.extendCoordinates(latLong);
		}
		try {
			byte zoom = LatLongUtils.zoomForBounds(mapView.getModel().mapViewDimension.getDimension(), bb, mapView.getModel().displayModel.getTileSize());
			// TODO adjust min/max zoom
			mapView.getModel().mapViewPosition.setMapPosition(new MapPosition(bb.getCenterPoint(), zoom));
		}
		catch (Exception e) {
			mapView.getModel().mapViewPosition.setCenter(latLong);
		}
	}

	public void inform(int event, Bundle extra) {
		switch (event) {
			case R.id.event_do_poi_new: {
				if (DEBUG) Log.d(TAG, "Poi.inform event=event_poi_new, extra=" + extra);
				storePointPosition((Tabulae) getActivity(),
					extra.getString("name"),
					extra.getString("description"),
					extra.getDouble(LATITUDE),
					extra.getDouble(LONGITUDE),
					true);
				center();
			}
			break;
			case R.id.event_do_poi_list: {
				if (DEBUG) Log.d(TAG, "Poi.inform event=event_poi_list, extra=" + extra);
				hideAll();
			}
			break;
		}
	}
} public class PoiItem extends StoreObject {
	// @Unique @NotNull
	String name;
	String description;
	double latitude;
	double longitude;
	boolean visible;

	public PoiItem() {
	}

	public PoiItem(String name, String description, double latitude, double longitude, boolean visible) {
		this.name = name;
		this.description = description;
		this.latitude = latitude;
		this.longitude = longitude;
		this.visible = visible;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public double getLatitude() {
		return latitude;
	}

	public void setLatitude(double latitude) {
		this.latitude = latitude;
	}

	public double getLongitude() {
		return longitude;
	}

	public void setLongitude(double longitude) {
		this.longitude = longitude;
	}

	public boolean isVisible() {
		return visible;
	}

	public void setVisible(boolean visible) {
		this.visible = visible;
	}
} public class ScreenCaptureFragment extends Base {
	private static final String STATE_ENABLED = "screencapture_enabled";
	private static final String STATE_RESULT_CODE = "screencapture_result_code";
	private static final String STATE_RESULT_DATA = "screencapture_result_data";
	protected boolean enabled;
	protected int mScreenWidth;
	protected int mScreenHeight;
	protected int mScreenDensity;
	protected MediaProjectionManager mMediaProjectionManager;
	protected MediaProjection mMediaProjection;
	protected VirtualDisplay mVirtualDisplay;
	protected MediaRecorder mMediaRecorder;
	private int mResultCode;
	private Intent mResultData;

	@Override public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (de.emdete.tabulae.Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onCreate");
		if (savedInstanceState != null) {
			enabled = savedInstanceState.getBoolean(STATE_ENABLED);
			mResultCode = savedInstanceState.getInt(STATE_RESULT_CODE);
			mResultData = savedInstanceState.getParcelable(STATE_RESULT_DATA);
		}
	}

	@Override public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onActivityCreated enabled=" + enabled + ", mVirtualDisplay=" + mVirtualDisplay);
		Activity activity = getActivity();
		DisplayMetrics mDisplayMetrics = new DisplayMetrics();
		activity.getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
		mScreenDensity = mDisplayMetrics.densityDpi;
		mScreenWidth = mDisplayMetrics.widthPixels;
		mScreenHeight = mDisplayMetrics.heightPixels;
		mMediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
		mMediaRecorder = new MediaRecorder();
		if (enabled && mVirtualDisplay == null) {
			go();
		}
	}

	@Override public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onSaveInstanceState mResultData=" + mResultData + ", mResultCode=" + mResultCode);
		outState.putBoolean(STATE_ENABLED, enabled);
		if (mResultData != null) {
			outState.putInt(STATE_RESULT_CODE, mResultCode);
			outState.putParcelable(STATE_RESULT_DATA, mResultData);
		}
	}

	@Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
		if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onActivityResult resultCode=" + resultCode + ", requestCode=" + requestCode + ", resultData=" + resultData);
		switch (requestCode) {
			case R.id.activity_result_id_screencapture: {
				switch (resultCode) {
					case Activity.RESULT_OK: {
						enabled = true;
						mResultCode = resultCode;
						mResultData = resultData;
						if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onActivityResult: permission granted enabled=" + enabled);
						if (getActivity() != null) {
							go();
						}
					}
					break;
					default:
						if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onActivityResult: permission denied");
						stopRecording();
						Toast.makeText(getActivity(), "User canceled", Toast.LENGTH_SHORT).show();
				}
			}
			default:
				super.onActivityResult(requestCode, resultCode, resultData);
		}
	}

	@Override public void onDestroy() {
		super.onDestroy();
		if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onDestroy");
		if (mMediaProjection != null) {
			try {
				mMediaProjection.stop();
			}
			catch (Exception ignore) {
				Log.e(Constants.TAG, "ScreenCaptureFragment.onDestroy:", ignore);
			}
			finally {
				mMediaProjection = null;
			}
		}
	}

	@Override public void onPause() {
		super.onPause();
		stopRecording();
	}

	protected void go() {
		if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.go mResultData=" + mResultData + ", mResultCode=" + mResultCode);
		if (getActivity() != null) {
			if (mResultData != null && mMediaProjection == null) {
				mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
				// mMediaProjection.registerCallback(...
			}
			if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.go mMediaProjection=" + mMediaProjection);
			if (mMediaProjection != null && mVirtualDisplay == null) {
				try {
					if (Constants.DEBUG) Log.d(Constants.TAG, "mScreenWidth=" + mScreenWidth + ", mScreenHeight=" + mScreenHeight);
					mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
					// mMediaRecorder.setVideoEncodingBitRate(128 * 1000);
					// mMediaRecorder.setVideoFrameRate(12);
					mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
					mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
					mScreenWidth = ((mScreenWidth + 15) / 16) * 16; // round up to multiple of 16
					mMediaRecorder.setVideoSize(mScreenWidth, mScreenHeight);
					mMediaRecorder.setOutputFile(getMovieName().getPath());
					mMediaRecorder.prepare();
					mMediaRecorder.start();
					mVirtualDisplay = mMediaProjection.createVirtualDisplay(
							getClass().getSimpleName(),
							mScreenWidth,
							mScreenHeight,
							mScreenDensity,
							DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
							mMediaRecorder.getSurface(),
							null, // Callbacks
							null // Handler
					);
					if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.onActivityResult: recording started");
					Toast.makeText(getActivity(), "Recording started", Toast.LENGTH_SHORT).show();
				}
				catch (Exception e) {
					Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_SHORT).show();
					Log.e(Constants.TAG, "ScreenCaptureFragment.onActivityResult:", e);
					stopRecording();
				}
			}
		}
	}

	protected void startRecording() {
		if (mVirtualDisplay != null) {
			stopRecording();
		}
		if (mMediaProjection == null) {
			getActivity().startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), R.id.activity_result_id_screencapture);
			enabled = true;
			notice(R.id.event_notify_screencapture, "enabled", enabled);
			if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.startRecording: ask for permission enabled=" + enabled);
		} else {
			go();
		}
	}

	protected void stopRecording() {
		enabled = false;
		notice(R.id.event_notify_screencapture, "enabled", enabled);
		if (mVirtualDisplay != null) {
			try {
				mVirtualDisplay.release();
			}
			catch (Exception ignore) {
				// Log.e(TAG, "ScreenCaptureFragment.stopRecording:", ignore);
			}
			finally {
				mVirtualDisplay = null;
			}
		}
		if (mMediaRecorder != null) {
			try {
				mMediaRecorder.stop();
			}
			catch (Exception ignore) {
				// Log.e(TAG, "ScreenCaptureFragment.stopRecording:", ignore);
			}
			finally {
				mMediaRecorder.reset();
				mMediaRecorder.release();
			}
		}
		if (Constants.DEBUG) Log.d(Constants.TAG, "ScreenCaptureFragment.stopRecording: recording stopped");
	}

	File getMovieName() {
		File f;
		do {
			Constants.ISODATEFORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
			f = new File(((Tabulae) getActivity()).getMoviesDir(), Constants.ISODATEFORMAT.format(new Date()) + "-capture.mp4");
		}
		while (f.exists());
		if (Constants.DEBUG) Log.d(Constants.TAG, "getMovieName f=" + f);
		return f;
	}

	public void inform(int event, Bundle extra) {
		switch (event) {
			case R.id.event_request_screencapture: {
				notice(R.id.event_notify_screencapture, "enabled", enabled);
			}
			break;
			case R.id.event_do_screencapture: {
				if (enabled) {
					stopRecording();
					Toast.makeText(getActivity(), "Recording stopped", Toast.LENGTH_SHORT).show();
				} else {
					startRecording();
				}
			}
			break;
		}
	}
} public class TrackItem extends StoreObject {
	static public final int CATEGORY_IMPORTED = 32122;
	static public final int CATEGORY_RECORDED = 52813;
	static public final int CATEGORY_TRAFFIC = 12910;
	String name; // @Unique @NotNull
	String description;
	String comment;
	Date timestamp;
	boolean visible = true;
	int pointcount = -1;
	long duration = -1;
	long distance = -1;
	int categoryid = -1;
	int activityid = -1;
	int cropto = -1;
	int cropfrom = -1;
	volatile List<TrackPointItem> trackPointItems;

	public TrackItem() {
	}

	public TrackItem(String name, String description) {
		this.name = name;
		this.description = description;
		trackPointItems = null;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public void add(SQLiteDatabase db, TrackPointItem trackPointItem) throws Exception {
		//trackPointItem.track = this;
		getTrackPointItems(db).add(trackPointItem);
		if (getId() >= 0) {
			trackPointItem.trackId = getId();
			if (db != null) {
				trackPointItem.insert(db);
			}
		}
	}

	public List<TrackPointItem> getTrackPointItems(SQLiteDatabase db) throws Exception {
		if (trackPointItems == null) {
			if (getId() >= 0) {
				trackPointItems = (List)query(db, TrackPointItem.class).where("trackId").equal(getId()).fetchAll();
			}
			else {
				trackPointItems = new ArrayList<>();
			}
		}
		return trackPointItems;
	}

	public StoreObject insert(SQLiteDatabase db) throws Exception {
		super.insert(db);
		if (trackPointItems != null) {
			for (TrackPointItem trackPointItem : getTrackPointItems(db)) {
				if (trackPointItem.trackId >= 0 && trackPointItem.trackId != getId()) {
					throw new Exception("assertion failed id=" + getId() + ", point.trackId=" + trackPointItem.trackId);
				}
				trackPointItem.trackId = getId();
				trackPointItem.insert(db);
			}
		}
		return this;
	}

	public List<LatLong> getTrackLatLongs(SQLiteDatabase db) throws Exception {
		List<LatLong> list = new ArrayList<>();
		for (TrackPointItem trackPointItem : getTrackPointItems(db)) {
			list.add(new LatLongTagged(trackPointItem));
		}
		return list;
	}

	public Date getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(Date timestamp) {
		this.timestamp = timestamp;
	}

	public boolean isVisible() {
		return visible;
	}

	public void setVisible(boolean visible) {
		this.visible = visible;
	}

	public int getPointcount() {
		return pointcount;
	}

	public void setPointcount(int pointcount) {
		this.pointcount = pointcount;
	}

	public long getDuration() {
		return duration;
	}

	public void setDuration(long duration) {
		this.duration = duration;
	}

	public long getDistance() {
		return distance;
	}

	public void setDistance(long distance) {
		this.distance = distance;
	}

	public int getCategoryid() {
		return categoryid;
	}

	public void setCategoryid(int categoryid) {
		this.categoryid = categoryid;
	}

	public int getActivityid() {
		return activityid;
	}

	public void setActivityid(int activityid) {
		this.activityid = activityid;
	}

	public int getCropto() {
		return cropto;
	}

	public void setCropto(int cropto) {
		this.cropto = cropto;
	}

	public int getCropfrom() {
		return cropfrom;
	}

	public void setCropfrom(int cropfrom) {
		this.cropfrom = cropfrom;
	}

	public String getComment() {
		return comment;
	}

	public void setComment(String comment) {
		this.comment = comment;
	}

	public static int deleteCategory(SQLiteDatabase db, int categoryid) throws Exception {
		return StoreObject.query(db, TrackItem.class)
			.where("categoryid").equal(categoryid)
			.delete();
	}

	public static class LatLongTagged extends LatLong {
		public TrackPointItem trackPointItem;

		public LatLongTagged(TrackPointItem trackPointItem) {
			super(trackPointItem.latitude, trackPointItem.longitude);
			this.trackPointItem = trackPointItem;
		}
	}
} public class TrackPointItem extends StoreObject {
	// @Unique @NotNull
	int sequence;
	Date timestamp;
	double latitude;
	double longitude;
	double altitude;
	double speed;
	int attribute;
	long trackId = -1;
	//TrackItem track;

	public TrackPointItem() {
	}

	public TrackPointItem(double latitude, double longitude) {
		this.latitude = latitude;
		this.longitude = longitude;
	}

	public int getSequence() {
		return sequence;
	}

	public void setSequence(int sequence) {
		this.sequence = sequence;
	}

	public TrackItem getMeta(SQLiteDatabase db) throws Exception {
		return (TrackItem)query(db, TrackItem.class).where("id").equal(trackId).fetchOne();
	}

	public double getLatitude() {
		return latitude;
	}

	public void setLatitude(double latitude) {
		this.latitude = latitude;
	}

	public double getLongitude() {
		return longitude;
	}

	public void setLongitude(double longitude) {
		this.longitude = longitude;
	}

	public double getAltitude() {
		return altitude;
	}

	public void setAltitude(double altitude) {
		this.altitude = altitude;
	}

	public double getSpeed() {
		return speed;
	}

	public void setSpeed(double speed) {
		this.speed = speed;
	}

	public Date getTimestamp() {
		return timestamp;
	}

	public void setTimestamp(Date timestamp) {
		this.timestamp = timestamp;
	}

	public int getAttribute() {
		return attribute;
	}

	public void setAttribute(int attribute) {
		attribute %= 5;
		this.attribute = attribute;
	}

	public LatLong getLatLon() {
		return new LatLong(latitude, longitude);
	}
} public class Traffic extends Base {
/*
// see https://developer.android.com/reference/android/os/Handler.html
	Handler handler = new Handler();
		handler.postDelayed(new Runnable() {
			@Override public void run() {
				Intent intent = new Intent(ActivityName.this, ThirdQuestion.class);
				startActivity(intent);
			}
		}, 2000);
// see https://developer.android.com/reference/android/app/AlarmManager.html
*/
	protected ExecutorService mThreadPool = Executors.newSingleThreadExecutor(new ThreadFactory() {
		@Override public Thread newThread(@NonNull Runnable r) {
			return new Thread(r, "inform");
		}
	});
	protected boolean enabled = false;

	public void inform(int event, Bundle extra) {
		//if (DEBUG) Log.d(TAG, "Track.inform event=" + event + ", extra=" + extra);
		switch (event) {
			case R.id.event_request_traffic: {
				notice(R.id.event_notify_traffic, "enabled", enabled);
			}
			break;
			case R.id.event_do_traffic: {
				if (!enabled) {
					enabled = true;
					try {
						mThreadPool.execute(new Runnable() {
							public void run() {
								try {
									final File cache_dir = new File(((Tabulae) getActivity()).getBaseDir(), "cache");
									//noinspection ResultOfMethodCallIgnored
									cache_dir.mkdirs();
									try (final SQLiteDatabase db = ((Tabulae)getActivity()).getWritableDatabase()) {
										// remove old incidents
										int count = TrackItem.deleteCategory(db, TrackItem.CATEGORY_TRAFFIC);
										if (DEBUG) Log.d(TAG, "deleteCategory=" + count);
									}
									((Tabulae)getActivity()).asyncInform(R.id.event_do_track_list, null);
									// retrieve new report
									ReportRetriever.Incidents incidents = ReportRetriever.go(cache_dir, null);
									try (final SQLiteDatabase db = ((Tabulae)getActivity()).getWritableDatabase()) {
										for (ReportRetriever.Incident incident: incidents) {
											Log.d(TAG, "incident=" + incident);
											TrackItem trackItem = new TrackItem(incident.getName(), incident.getDescription());
											trackItem.setCategoryid(TrackItem.CATEGORY_TRAFFIC);
											for (Location position: incident.getPosition()) {
												trackItem.add(null, new TrackPointItem(position.getLatitude(), position.getLongitude()));
											}
											trackItem.insert(db);
										}
									}
									enabled = false;
									//notice(R.id.event_notify_traffic, "enabled", enabled);
									((Tabulae)getActivity()).asyncInform(R.id.event_do_track_list, null);
								}
								catch (Exception e) {
									Log.d(TAG, "traffic load e=" + e, e);
									//Toast.makeText(getActivity().getApplicationContext(), e.toString(), Toast.LENGTH_LONG).show();
								}
							}
						});
					}
					catch (Exception e) {
						Log.d(TAG, "traffic load e=" + e);
					}
				}
			}
			break;
		}
	}
} public class StoreObject {
	private static final SimpleDateFormat ISO_DATE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
	private static final String TAG = "de.emdete";
	/**
	 * supported java datatypes. most native types plus string and date.
	 */
	private static final Set<Class> SUPPORTED_TYPES = new HashSet<Class>(Arrays.asList(new Class[] {
		String.class,
		Date.class,
		Boolean.TYPE,
		Double.TYPE,
		Float.TYPE,
		Long.TYPE,
		Integer.TYPE,
		Short.TYPE,
		}));
	private static final Map<Class, Field[]> FIELDS_CACHE = new WeakHashMap<Class, Field[]>();
	private static final String ID_WHERE = "id = ?";
	static {
		ISO_DATE.setTimeZone(TimeZone.getTimeZone("UTC"));
	}

	/**
	 * primary key for all StoreObject
	 */
	protected long id = -1l;

	public long getId() {
		return id;
	}

	/**
	 * method to determine a list of fields that will be mapped to a sql table
	 * column. the result will be cached in a WeakHashMap.
	 */
	static Field[] storedFields(Class clazz) {
		Field[] fields = FIELDS_CACHE.get(clazz);
		if (fields == null) {
			Class zuper = clazz.getSuperclass();
			List<Field> _fields;
			if (zuper != Object.class) {
				_fields = new ArrayList<>(Arrays.asList(storedFields(zuper)[0]));
			}
			else {
				_fields = new ArrayList<>();
			}
			for (Field field: clazz.getDeclaredFields()) {
				//if ((field.getModifiers() & Modifier.PROTECTED) == 0) continue;
				if ((field.getModifiers() & (0
					|Modifier.STATIC
					|Modifier.VOLATILE
					)) != 0)
					continue;
				if ((field.getModifiers() & Modifier.PUBLIC) == 0)
					field.setAccessible(true);
				if (field.getName().startsWith("this$")) // outer class reference :)
					continue;
				if (!SUPPORTED_TYPES.contains(field.getType()))
					continue;
				_fields.add(field);
			}
			fields = _fields.toArray(new Field[0]);
			FIELDS_CACHE.put(clazz, fields);
		}
		return fields;
	}

	/**
	 *
	 */
	static String[] getProjection(Class clazz) {
		int i = 0;
		Field[] fields = storedFields(clazz);
		String[] ret = new String[fields.length];
		for (Field field: fields) {
			ret[i++] = field.getName();
		}
		//Log.d(TAG, "projection=" + Arrays.toString(ret));
		return ret;
	}

	/**
	 *
	 */
	ContentValues toContentValues(ContentValues contentValues) throws Exception {
		for (Field field: storedFields(this.getClass())) {
			Class clazz = field.getType();
			if ("id".equals(field.getName())) {
				; // ignore
			}
			else if (clazz == String.class) {
				String v = (String)field.get(this);
				if (v != null)
					contentValues.put(field.getName(), v);
			}
			else if (clazz == Boolean.TYPE) {
				contentValues.put(field.getName(), field.getBoolean(this)?"true":"false");
			}
			else if (clazz == Double.TYPE) {
				contentValues.put(field.getName(), field.getDouble(this));
			}
			else if (clazz == Float.TYPE) {
				contentValues.put(field.getName(), field.getFloat(this));
			}
			else if (clazz == Long.TYPE) {
				contentValues.put(field.getName() ,field.getLong(this));
			}
			else if (clazz == Integer.TYPE) {
				contentValues.put(field.getName(), field.getInt(this));
			}
			else if (clazz == Short.TYPE) {
				contentValues.put(field.getName(), field.getShort(this));
			}
			else if (clazz == Date.class) {
				Date v = (Date)field.get(this);
				if (v != null)
					contentValues.put(field.getName(), ISO_DATE.format(v));
			}
			else {
				throw new Exception("unsupported clazz=" + clazz.getName());
			}
		}
		return contentValues;
	}

	/**
	 *
	 */
	StoreObject fromCursor(Cursor cursor) throws Exception {
		Field[] fields = storedFields(this.getClass());
		for (int i=0;i<fields.length;i++) {
			Class clazz = fields[i].getType();
			if (cursor.isNull(i))
				fields[i].set(this, null);
			else if (clazz == String.class)
				fields[i].set(this, cursor.getString(i));
			else if (clazz == Boolean.TYPE)
				fields[i].setBoolean(this, "true".equalsIgnoreCase(cursor.getString(i)));
			else if (clazz == Double.TYPE)
				fields[i].setDouble(this, cursor.getDouble(i));
			else if (clazz == Float.TYPE)
				fields[i].setFloat(this, cursor.getFloat(i));
			else if (clazz == Long.TYPE)
				fields[i].setLong(this, cursor.getLong(i));
			else if (clazz == Integer.TYPE)
				fields[i].setInt(this, cursor.getInt(i));
			else if (clazz == Short.TYPE)
				fields[i].setShort(this, cursor.getShort(i));
			else if (clazz == Date.class)
				fields[i].set(this, ISO_DATE.parse(cursor.getString(i)));
			else
				throw new Exception("unsupported clazz=" + clazz.getName());
		}
		return this;
	}

	/**
	 *
	 */
	protected StoreObject add(SQLiteDatabase db, List list, StoreObject item) throws Exception {
		list.add(item);
		return item.insert(db);
	}

	/**
	 * create the table in the database.
	 */
	public static String create(SQLiteDatabase db, Class clazz) {
		String ret = "CREATE TABLE ";
		ret += clazz.getSimpleName();
		ret += " (";
		for (Field field: storedFields(clazz)) {
			if ("id".equals(field.getName())) {
				ret += "id INTEGER PRIMARY KEY AUTOINCREMENT";
			}
			else {
				ret += ", ";
				ret += field.getName();
			}
		}
		ret += ")";
        if (db != null) {
			db.execSQL(ret);
		}
		return ret;
	}
	/**
	 * alter the table in the database to fit class. warning: total data loss!
	 * TODO not implemented properly yet: drops&creates for now
	 */
	public static String alter(SQLiteDatabase db, Class clazz) {
		try {
			drop(db, clazz);
		}
		catch (android.database.sqlite.SQLiteException e) {
		}
		create(db, clazz);
		return null;
	}

	/**
	 * insert this record into the database.
	 */
	public StoreObject insert(SQLiteDatabase db) throws Exception {
		if (id >= 0) {
			throw new Exception("id=" + id);
		}
		id = db.insert(this.getClass().getSimpleName(), "null", toContentValues(new ContentValues()));
		// Log.d(TAG, "new item id=" + id);
		return this;
	}

	/**
	 * selects records from the database. mainly intern usage, use query object
	 * to control where and order filters.
	 */
	public static List<StoreObject> select(SQLiteDatabase db, Class clazz, String where, String[] values, String order) throws Exception {
		//Log.d(TAG, "StoreObject.select select=" + Arrays.toString(getProjection(clazz)) + ", from=" + clazz.getSimpleName() + ", where=" + where + ", values=" + Arrays.toString(values));
		List<StoreObject> list = new ArrayList<StoreObject>();
        try (final Cursor cursor = db.query(clazz.getSimpleName(),
			getProjection(clazz),
			where, // where
			values, // values
			null, // group
			null, // having
			order)) { // order
			while (cursor.moveToNext()) {
				list.add(((StoreObject)clazz.getConstructor().newInstance()).fromCursor(cursor));
			}
		}
		// Log.d(TAG, "StoreObject.select list.size=" + list.size());
		return list;
	}

	/**
	 * select records from the database. all records for one class will be derived.
	 */
	public static List<StoreObject> select(SQLiteDatabase db, Class clazz) throws Exception {
        return select(db, clazz, null, null, "id");
	}

	/**
	 * start a query. creates a query objects where filter can be applied and
	 * finally a fetch can be done.
	 */
	public static Query query(SQLiteDatabase db, Class clazz) throws Exception {
		return new Query(db, clazz);
	}

	/**
	 * query object that holds the data to finally issue the query.
	 */
	public static class Query {
		SQLiteDatabase db;
		Class clazz;
		String where;
		List<String> values = new ArrayList<String>();
		String order;

		Query(SQLiteDatabase db, Class clazz) {
			this.db = db;
			this.clazz = clazz;
		}

		public Query where(String column) {
			// TODO check column
			where = column;
			return this;
		}

		public Query equal(Object o) {
			where += " = ";
			if (o instanceof String) {
				where += '?';
				values.add(o.toString());
			}
			else if (o instanceof Boolean) {
				where += (Boolean)o?"'true'":"'false'";
			}
			else if (o instanceof Date) {
				where += '?';
				values.add(ISO_DATE.format((Date)o));
			}
			else {
				where += o;
			}
			return this;
		}

		public Query like(Object o) {
			where += " LIKE ?";
			values.add(o.toString());
			return this;
		}

		public Query and(String column) {
			// TODO check column
			where += " AND " + column;
			return this;
		}

		public Query order_by(String column) {
			// TODO check column
			if (order == null) {
				order = column;
			}
			else {
				order += ", " + column;
			}
			return this;
		}

		public StoreObject fetchOne() throws Exception {
			StoreObject obj = null;
			try (final Cursor cursor = db.query(clazz.getSimpleName(),
				getProjection(clazz),
				where, // where
				values.toArray(new String[0]), // values
				null, // group
				null, // having
				order)) { // order
				if (!cursor.moveToNext()) {
					throw new Exception("none found");
				}
				obj = ((StoreObject)clazz.getConstructor().newInstance()).fromCursor(cursor);
				if (cursor.moveToNext()) {
					throw new Exception("not unique");
				}
			}
			return obj;
		}

		public List<StoreObject> fetchAll() throws Exception {
			return select(db, clazz, where, values.toArray(new String[0]), order);
		}

		public int delete() throws Exception {
			return db.delete(clazz.getSimpleName(), where, values.toArray(new String[0]));
		}

		public long count() {
			return -1l;
		}
	}

	/**
	 * update this record in the database.
	 */
	public StoreObject update(SQLiteDatabase db) throws Exception {
		if (id < 0) {
			throw new Exception("id=" + id);
		}
        if (db.update(this.getClass().getSimpleName(), toContentValues(new ContentValues()), ID_WHERE, new String[]{String.valueOf(id)}) != 1) {
			throw new Exception("did not update 1");
		}
		return this;
	}

	/**
	 * delete this record from the database.
	 */
	public void delete(SQLiteDatabase db) throws Exception {
		if (id < 0) {
			throw new Exception("id=" + id);
		}
        if (db.delete(this.getClass().getSimpleName(), ID_WHERE, new String[]{String.valueOf(id)}) != 1) {
			throw new Exception("did not delete 1");
		}
	}

	/**
	 * drop the table from the database.
	 */
	public static String drop(SQLiteDatabase db, Class clazz) {
		String ret = "DROP TABLE ";
		ret += clazz.getSimpleName();
        if (db != null) {
			db.execSQL(ret);
		}
		return ret;
	}
} public class Map extends Base {
	// get one from http://download.mapsforge.org/maps/ and adapt path to your needs:
	protected MapView mapView;
	protected int currentMap = -1;
	protected LayerBase layer;
	protected PreferencesFacade preferencesFacade;
	protected boolean snapToLocationEnabled;
	protected LatLong lastLocation;

	void activateLayer(int id) {
		if (layer == null || id != currentMap) {
			if (layer != null) {
				layer.onPause();
				layer.onDestroy();
				layer = null;
			}
			currentMap = id;
			switch (id) {
			case 0:
				layer = new LayerMapsForge((Tabulae) getActivity(), mapView);
				break;
			case 1:
				layer = new LayerOpenAndroMaps((Tabulae) getActivity(), mapView);
				break;
			case 2:
				layer = new LayerBingSat((Tabulae) getActivity(), mapView);
				break;
			case 3:
				layer = new LayerGoogleSat((Tabulae) getActivity(), mapView);
				break;
			case 4:
				layer = new LayerMapQuest((Tabulae) getActivity(), mapView);
				break;
			case 5:
				layer = new LayerOutdoorActive((Tabulae) getActivity(), mapView);
				break;
			case 6:
				layer = new LayerWanderreitKarte((Tabulae) getActivity(), mapView);
				break;
			}
			notifyMap();
			if (id != -1) {
				Editor editor = getPreferences().edit();
				editor.putInt("currentMap", currentMap);
				editor.apply();
			}
		}
		if (layer != null) {
			layer.setVisible(true);
			layer.onResume();
		}
	}

	@Override public void onCreate(Bundle bundle) {
		if (de.emdete.tabulae.Constants.DEBUG) Log.d(Constants.TAG, "Map.onCreate bundle=" + bundle);
		super.onCreate(bundle);
		// AndroidGraphicFactory.createInstance(getActivity().getApplication());
		mapView = new MapView(getActivity()) {
			@Override public boolean onTouchEvent(MotionEvent motionEvent) {
				if (snapToLocationEnabled) {
					notifyAutofollow(false);
				}
				return super.onTouchEvent(motionEvent);
			}
		};
		mapView.setClickable(true);
		// TODO: consider mapView.setGestureDetector();?
		mapView.getMapScaleBar().setVisible(false);
		mapView.getMapZoomControls().setShowMapZoomControls(false);
		mapView.setBuiltInZoomControls(false);
		//mapView.getModel().mapViewPosition.setZoomLevelMin((byte) 4);
		//mapView.getModel().mapViewPosition.setZoomLevelMax((byte) 20);
		//mapView.getModel().displayModel.setBackgroundColor(0xffbbbbbb);
		//mapView.getModel().displayModel.setUserScaleFactor(1.5f);
	}

	@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		if (Constants.DEBUG) Log.d(Constants.TAG, "Map.onCreateView");
		return mapView;
	}

	static public void onCreateOptionsMenu(Tabulae activity) {
		activity.setVisible(R.id.event_do_map_vector, LayerMapsForge.isAvaiable(activity));
		activity.setVisible(R.id.event_do_map_openandromaps, LayerOpenAndroMaps.isAvaiable(activity));
		activity.setVisible(R.id.event_do_map_bing_satellite, LayerBingSat.isAvaiable(activity));
		activity.setVisible(R.id.event_do_map_google_satellite, LayerGoogleSat.isAvaiable(activity));
		activity.setVisible(R.id.event_do_map_mapquest, LayerMapQuest.isAvaiable(activity));
		activity.setVisible(R.id.event_do_map_outdoor_active, LayerOutdoorActive.isAvaiable(activity));
		activity.setVisible(R.id.event_do_map_wanderreitkarte, LayerWanderreitKarte.isAvaiable(activity));
	}

	@Override public void onResume() {
		super.onResume();
		currentMap = getPreferences().getInt("currentMap", 0);
		preferencesFacade = new AndroidPreferences(getPreferences());
		mapView.getModel().init(preferencesFacade);
		notifyZoom();
		notifyAutofollow(getPreferences().getBoolean("autoFollow", false));
		if (snapToLocationEnabled) { // if snapToLocationEnabled was on map-center is the last location
			lastLocation = mapView.getModel().mapViewPosition.getCenter();
		}
		activateLayer(currentMap);
	}

	@Override public void onPause() {
		super.onPause();
		mapView.getModel().save(preferencesFacade);
		preferencesFacade.save();
		activateLayer(-1);
		mapView.getModel().mapViewPosition.destroy();
		mapView.destroy();
	}

	public MapView getMapView() {
		return mapView;
	}

	void notifyMap() {
		if (layer != null) {
			notice(R.id.event_notify_map, "current_map", layer.getId());
		}
	}

	void notifyZoom() {
		notice(R.id.event_notify_zoom, "zoom_level", mapView.getModel().mapViewPosition.getZoomLevel());
	}

	void notifyAutofollow(boolean newValue) {
		if (newValue != snapToLocationEnabled) {
			snapToLocationEnabled = newValue;
			notice(R.id.event_notify_autofollow, "autofollow", snapToLocationEnabled);
			Editor editor = getPreferences().edit();
			editor.putBoolean("autoFollow", snapToLocationEnabled);
			editor.apply();
			if (snapToLocationEnabled) {
				centerIfFollow();
			}
		}
	}

	void centerIfFollow() {
		if (lastLocation != null && snapToLocationEnabled) {
			//if (DEBUG) Log.d(TAG, "Map.inform lastLocation=" + lastLocation);
			mapView.getModel().mapViewPosition.setCenter(lastLocation);
		}
	}

	private LatLong toLatLong(Bundle extra) {
		return new LatLong(extra.getDouble(LATITUDE), extra.getDouble(LONGITUDE));
	}

	public void inform(int event, Bundle extra) {
		//if (DEBUG) Log.d(TAG, "Map.inform event=" + event + ", extra=" + extra);
		switch (event) {
			case R.id.event_do_autofollow: {
				//if (DEBUG) Log.d(TAG, "Map.inform event=event_autofollow, extra=" + extra);
				notifyAutofollow(!snapToLocationEnabled);
			}
			break;
			case R.id.event_request_autofollow: {
				notice(R.id.event_notify_autofollow, "autofollow", snapToLocationEnabled);
			}
			break;
			case R.id.event_notify_location: {
				if (extra.containsKey(LATITUDE) && extra.containsKey(LONGITUDE)) {
					//if (DEBUG) Log.d(TAG, "Map.inform event=location, extra=" + extra);
					lastLocation = toLatLong(extra);
					//if (DEBUG) Log.d(TAG, "Map.inform lastLocation=" + lastLocation);
					centerIfFollow();
				}
			}
			break;
			case R.id.event_do_zoom_in: {
				MapViewPosition mvp = mapView.getModel().mapViewPosition;
				mvp.setZoomLevel((byte) (mvp.getZoomLevel() + 1));
				notifyZoom();
			}
			break;
			case R.id.event_do_zoom_out: {
				MapViewPosition mvp = mapView.getModel().mapViewPosition;
				if (mvp.getZoomLevel() > 0) {
					mvp.setZoomLevel((byte) (mvp.getZoomLevel() - 1));
				}
				notifyZoom();
			}
			break;
			case R.id.event_request_zoom: {
				notifyZoom();
			}
			break;
			case R.id.event_request_map: {
				notifyMap();
			}
			break;
			case R.id.event_do_send_location: {
				final MapViewPosition mvp = mapView.getModel().mapViewPosition;
				final String label = "";
				final byte zoom = mvp.getZoomLevel();
				final LatLong latLong = mvp.getCenter();
				final Intent intent = new Intent(Intent.ACTION_SEND);
				intent.setType("text/plain");
				intent.putExtra(Intent.EXTRA_TEXT, label + '\n'
								+ "http://www.openstreetmap.org/?mlat=" + latLong.latitude
								+ "&mlon=" + latLong.longitude
								+ "#map=" + zoom
								+ '/' + latLong.latitude
								+ '/' + latLong.longitude
								+ "&layers=T"
				);
				startActivity(intent);
			}
			break;
			case R.id.event_do_view_location: {
				final MapViewPosition mvp = mapView.getModel().mapViewPosition;
				final String label = "";
				//final byte zoom = mvp.getZoomLevel();
				final LatLong latLong = mvp.getCenter();
				final Intent intent = new Intent(Intent.ACTION_VIEW);
				intent.setData(Uri.parse("geo:"
						+ latLong.latitude + ','
						+ latLong.longitude + "?q="
						+ latLong.latitude + ','
						+ latLong.longitude + '('
						+ label + ')'));
				startActivity(intent);
			}
			break;
			case R.id.event_do_map_vector:
				activateLayer(0);
				break;
			case R.id.event_do_map_openandromaps:
				activateLayer(1);
				break;
			case R.id.event_do_map_bing_satellite:
				activateLayer(2);
				break;
			case R.id.event_do_map_google_satellite:
				activateLayer(3);
				break;
			case R.id.event_do_map_mapquest:
				activateLayer(4);
				break;
			case R.id.event_do_map_outdoor_active:
				activateLayer(5);
				break;
			case R.id.event_do_map_wanderreitkarte:
				activateLayer(6);
				break;
		}
	}
} public class Track extends Base {
	List<Layer> polylines = new ArrayList<>();

	@Override
	public void onCreate(Bundle bundle) {
		if (de.emdete.tabulae.Constants.DEBUG) Log.d(Constants.TAG, "Track.onCreate");
		super.onCreate(bundle);
	}

	@Override
	public void onResume() {
		super.onResume();
		if (Constants.DEBUG) Log.d(Constants.TAG, "Track.onResume");
		showVisibleTracks();
	}

	@Override
	public void onPause() {
		super.onPause();
		if (Constants.DEBUG) Log.d(Constants.TAG, "Track.onPause");
		MapView mapView = ((Tabulae) getActivity()).getMapView();
		if (mapView != null) {
				if (mapView.getLayerManager() != null) {
				Layers layers = mapView.getLayerManager().getLayers();
				for (Layer polyline: polylines) {
					((AlternatingLine)polyline).getLatLongs().clear();
					layers.remove(polyline, false);
				}
			}
		}
		polylines.clear();
	}

	void showVisibleTracks() {
		if (Constants.DEBUG) Log.d(Constants.TAG, "Track.showVisibleTracks");
		try {
			MapView mapView = ((Tabulae) getActivity()).getMapView();
			Layers layers = mapView.getLayerManager().getLayers();
			if (Constants.DEBUG) Log.d(Constants.TAG, "showVisibleTracks layers.size=" + layers.size());
			for (Layer polyline: polylines) {
				((AlternatingLine)polyline).getLatLongs().clear();
				if (!layers.remove(polyline, false)) {
					Log.e(Constants.TAG, "showVisibleTracks remove did not remove layer?!?");
				}
			}
			polylines.clear();
			if (Constants.DEBUG) Log.d(Constants.TAG, "showVisibleTracks layers.size=" + layers.size());
			BoundingBox bb = null;
			try (final SQLiteDatabase db = ((Tabulae)getActivity()).getReadableDatabase()) {
				for (StoreObject item : StoreObject.query(db, TrackItem.class).where("visible").equal(true).fetchAll()) {
					// if (DEBUG) Log.d(TAG, "Track.showVisibleTracks item.description=" + ((TrackItem)item).getDescription());
					List<LatLong> latLongs = ((TrackItem)item).getTrackLatLongs(db);
					// if (DEBUG) Log.d(TAG, "Track.showVisibleTracks size=" + latLongs.size());
					if (latLongs.size() > 0) {
						if (bb == null) {
							bb = new BoundingBox(latLongs);
						}
						else {
							bb = bb.extendBoundingBox(new BoundingBox(latLongs));
						}
						AlternatingLine polyline = new AlternatingLine(AndroidGraphicFactory.INSTANCE);
						polyline.setLatLongs(latLongs);
						Bundle extra = new Bundle();
						extra.putBoolean("autofollow", false);
						((Tabulae) getActivity()).inform(R.id.event_do_autofollow, extra);
						polylines.add(polyline);
					}
				}
			}
			layers.addAll(polylines);
			if (Constants.DEBUG) Log.d(Constants.TAG, "showVisibleTracks layers.size=" + layers.size());
			// mapView.getModel().mapViewPosition.setMapPosition(new MapPosition(
			// 	bb.getCenterPoint(),
			// 	LatLongUtils.zoomForBounds(mapView.getModel().mapViewDimension.getDimension(),
			// 	bb,
			// 	mapView.getModel().displayModel.getTileSize())));
			// mapView.getModel().mapViewPosition.setCenter(latLongs.get(0));
		}
		catch (Exception e) {
			Log.e(Constants.TAG, "Track.showVisibleTracks e=" + e, e);
		}
	}

	public void importGpx() {
		File[] gpxs = ((Tabulae) getActivity()).getGpxDir().listFiles();
		if (gpxs != null) for (File gpx : gpxs) {
			if (gpx.isFile() && gpx.toString().endsWith(".gpx")) {
				try {
					if (Constants.DEBUG) Log.d(Constants.TAG, "Track.inform import gpx=" + gpx);
					try (final SQLiteDatabase db = ((Tabulae)getActivity()).getWritableDatabase()) {
						TrackGpxParser track = new TrackGpxParser(gpx, db);
						if (StoreObject.query(db, TrackItem.class).where("name").equal(track.trackItem.getName()).count() == 0) {
							track.trackItem.insert(db);
							if (Constants.DEBUG) Log.d(Constants.TAG, "Track.inform stored name=" + track.trackItem.getName());
						}
					}
				}
				catch (Exception e) {
					Log.e(Constants.TAG, "Track.inform", e);
				}
			}
		}
	}

	public void inform(int event, Bundle extra) {
		//if (DEBUG) Log.d(TAG, "Track.inform event=" + event + ", extra=" + extra);
		switch (event) {
			case R.id.event_do_track_list: {
				showVisibleTracks();
			}
			break;
		}
	}
} public interface Constants {
	String TAG = "de.emdete.tabulae";
	String USER_AGENT = "Tabulae/" + BuildConfig.VERSION_NAME + " (Android " + Build.VERSION.RELEASE + ")";
	boolean DEBUG = BuildConfig.DEBUG;
	// static { DEBUG = Log.isLoggable("de.emdete.android", Log.DEBUG); }
	float USER_FONT_FACTOR = 1.3f; // TODO: determine sp/dp factor somehow
	String ACTION_CONVERSATIONS_REQUEST = "eu.siacs.conversations.location.request";
	String ACTION_CONVERSATIONS_SHOW = "eu.siacs.conversations.location.show";
	String GEO = "geo";
	String HTTPS = "https";
	String HTTP = "http";
	String LATITUDE = "latitude";
	String LONGITUDE = "longitude";
	String ALTITUDE = "altitude";
	String ACCURACY = "accuracy";
	String JID = "jid";
	String NAME = "name";
	String DESCRIPTION = "description";
	SimpleDateFormat ISODATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z+0000'", Locale.UK); // ISO8601
}
import android.content.ComponentName;
import android.content.Context;
import de.emdete.tabulae.fawlty.Fawlty;
import de.emdete.tabulae.gui.Controller;
import de.emdete.tabulae.gui.Dashboard;
import de.emdete.tabulae.gui.DocumentAvtivity;
import de.emdete.tabulae.locus.Locus;
import de.emdete.tabulae.poi.Poi;
import de.emdete.tabulae.poi.PoiItem;
import de.emdete.tabulae.screencapture.ScreenCaptureFragment;
import de.emdete.tabulae.track.TrackItem;
import de.emdete.tabulae.track.TrackPointItem;
import de.emdete.tabulae.traffic.Traffic;
import de.emdete.thinstore.StoreObject;
import android.app.Activity;
import android.app.FragmentManager;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.WindowManager;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
import de.emdete.tabulae.map.Map;
import de.emdete.tabulae.track.Track;
import static de.emdete.tabulae.Constants.*;
package de.emdete.tabulae;


public class Tabulae extends Activity {
	protected Base[] fragments;
	protected Menu menu;
	protected File baseStorageFile = null;
	protected ExecutorService mThreadPool = Executors.newSingleThreadExecutor(new ThreadFactory() {
		@Override
		public Thread newThread(Runnable r) {
			return new Thread(r, "inform");
		}
	});
	SQLiteOpenHelper dbHelper;

	static private String deKay(long l) {
		double d = l;
		final String[] fs = new String[]{"%.2fB", "%.2fKB", "%.2fMB", "%.2fGB",};
		for (String f : fs) {
			if (d < 1024.0) {
				return String.format(Locale.US, f, d);
			}
			d /= 1024.0;
		}
		return String.format(Locale.US, "%.2fTB", d);
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		if (DEBUG) Log.d(TAG, "Tabulae.onCreate");
		Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
			@Override
			public void uncaughtException(Thread thread, Throwable e) {
				Log.e(TAG, "error e=" + e, e);
				finish();
			}
		});
		dbHelper = new SQLiteOpenHelper(getApplicationContext(), "tabulae.db", null, 3){
			@Override public void onCreate(SQLiteDatabase db) {
				if (DEBUG) Log.d(TAG, "Tabulae.onCreate.SQLiteOpenHelper.onCreate");
				//Log.d(TAG, "create=" +
				StoreObject.create(db, PoiItem.class);
				StoreObject.create(db, TrackItem.class);
				StoreObject.create(db, TrackPointItem.class);
			}
			@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
				if (DEBUG) Log.d(TAG, "Tabulae.onCreate.SQLiteOpenHelper.onUpgrade");
				StoreObject.alter(db, PoiItem.class);
				StoreObject.alter(db, TrackItem.class);
				StoreObject.alter(db, TrackPointItem.class);
			}
		};
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		setContentView(R.layout.tabulae);
		fragments = new Base[]{
				new Map(),
				new Track(),
				new Poi(),
				new Fawlty(),