Offline Editor

You can access and edit data on your Android device even when you have no internet connection. The purpose of this sample is to demonstrate how to download feature data for offline use, edit the features geometry with feature templates, and synchronize your edits with a ArcGIS Server Feature Service.

The sample includes the following utility classes:

  • GDBUtil
  • TemplatePicker.

Sample Design:

The OfflineEditorActivity class sets up the layout and default map layers in the onCreate() method. The map layers and their service end points are defined in GDBUtil.java. The layout provides the action bar with Download/Online and Edit buttons.

Buttons

  • Download - This button allows you to download feature data from the online feature service and provision the data locally to your device as a .geodatabase file. Once the download is complete the app automatically updates with the downloaded feature data overlayed with the tile package (*.tpk if you have this installed locally on the device. The download button is replaced with an online button when you are in offline mode while the Edit button becomes available.
  • Edit - Opens the edit tools Contextual Action Bar.
    • Edit Template button opens the *Feature Templates dialog to allow you to select a feature template to edit the map with.
    • Save
    • Undo
    • Discard
    • Synchronize
    • Check button closes the edit tools contextual action bar

User Workflow

Edit

To get started you need to download the feature data to your device using the Download button. The app will automatically switch to data provisioned on your device once the download is complete. Next you can start to edit the feature data by tapping the Edit button which opens the edit tools. Using the edit tools, tap on the edit template button to select a feature template and place your feature template on the map. The geometry editing process is similar to the one documented in the GeometryEditor Sample. You can tap on the map to perform the edits and once the action is performed you have a an option to Save or Undo the last action.

Sync

Once the edits are performed locally, you can click on the Synchronize button to sync the local edits to the online Feature Service. You also have the option to discard all the changes made when tapping the Discard button.

Switch between On/Offline

When you return back to the initial ActionBar you can decide to go back online using the Online button, this then allows you to delete previously downloaded geodatabases so you can start again, or leave the data in place, in which case it will be used next time you go offline.

Sample Requirements

The OfflineEditor sample depends on the Android Support Library. Instructions for setting that it up prior to running the app is detailed below.

Steps

  1. Create a new Sample project for OfflineEditor. Refer to the How to work with samples documents to create a new sample project.
  2. Right click the sample project and select Android Tools > Add Support Library
  3. Accept packages to install and click Install
  4. Under Android Dependencies you should see the android-support-v4.jar file library
  5. Right click the sample project and select Properties
  6. Select the Java Build Path on the left hand side then select Order and Export in the Java Build Path tabs
  7. Make sure Android Dependencies is checked
  8. Run the application

Data:

The OfflineEditor sample is an example of working with data in an offline setting. The sample depends on basemap data to be located on the device. This includes installing a local tile map cache (tpk) to device as described below:

  1. Download Basemap data from ArcGIS Online - http://www.arcgis.com/home/item.html?id=9a7e015149854c35b5eb94494f4aeb89

  2. Create the the sample data folder at the root <storage> folder on your device, /{device-externalstoragepath}/ArcGIS/samples/OfflineEditor.

  3. Push the downloaded basemap from step 1, SanFrancisco.tpk to your device. /{device-externalstoragepath}/ArcGIS/samples/OfflineEditor/SanFrancisco.tpk

    Eg: /mnt/sdcard/ArcGIS/samples/OfflineEditor/SanFrancisco.tpk, or /storage/emulated/0/ArcGIS/samples/OfflineEditor/SanFrancisco.tpk

This sample will create offline files in the directory set up above which represents the features selected in the app for offline usage.

Sample Code

package com.esri.arcgis.android.samples.offlineeditor;

/* Copyright 2014 ESRI
 *
 * All rights reserved under the copyright laws of the United States
 * and applicable international laws, treaties, and conventions.
 *
 * You may freely redistribute and use this sample code, with or
 * without modification, provided you include the original copyright
 * notice and use restrictions.
 *
 * See the sample code usage restrictions document for further information.
 *
 */

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;

import com.esri.android.map.FeatureLayer;
import com.esri.android.map.GraphicsLayer;
import com.esri.android.map.Layer;
import com.esri.android.map.MapOnTouchListener;
import com.esri.android.map.MapView;
import com.esri.android.map.ags.ArcGISFeatureLayer;
import com.esri.android.map.ags.ArcGISTiledMapServiceLayer;
import com.esri.android.map.event.OnStatusChangedListener;
import com.esri.core.geodatabase.GeodatabaseFeature;
import com.esri.core.geodatabase.GeodatabaseFeatureTable;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.GeometryEngine;
import com.esri.core.geometry.MultiPath;
import com.esri.core.geometry.Point;
import com.esri.core.geometry.Polygon;
import com.esri.core.geometry.Polyline;
import com.esri.core.map.FeatureTemplate;
import com.esri.core.map.Graphic;
import com.esri.core.symbol.SimpleFillSymbol;
import com.esri.core.symbol.SimpleLineSymbol;
import com.esri.core.symbol.SimpleMarkerSymbol;
import com.esri.core.symbol.Symbol;
import com.esri.core.table.TableException;

/**
 * Allows you to make edits on the map being offline.
 */
public class OfflineEditorActivity extends Activity {

	protected static final String TAG = "OfflineEditorActivity";

	private static final int POINT = 0;

	private static final int POLYLINE = 1;

	private static final int POLYGON = 2;

	private static MapView mapView;

	GraphicsLayer graphicsLayer;

	GraphicsLayer graphicsLayerEditing;

	GraphicsLayer highlightGraphics;

	boolean featureUpdate = false;

	boolean mDatabaseInitialized = false;

	boolean onlineData = true;

	long featureUpdateId;

	int addedGraphicId;

	MyTouchListener myListener;

	private TemplatePicker tp;

	ArrayList<Point> points = new ArrayList<Point>();

	ArrayList<Point> mpoints = new ArrayList<Point>();

	boolean midpointselected = false;

	boolean vertexselected = false;

	int insertingindex;

	int editingmode;

	static ProgressDialog progress;

	MenuItem editMenuItem;

	MenuItem offlineMenuItem;

	MenuItem onlineMenuItem;

	ArrayList<EditingStates> editingstates = new ArrayList<EditingStates>();

	FeatureTemplate template;

	@SuppressWarnings("unused")
	private Menu menu;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.offlineeditor);

		/*
		 * Initialize ArcGIS Android MapView, tiledMapServiceLayer, and Graphics
		 * Layer
		 */
		mapView = ((MapView) findViewById(R.id.map));
		mapView.addLayer(new ArcGISTiledMapServiceLayer(
				GDBUtil.DEFAULT_BASEMAP_SERVICE_URL));

		for (int i : GDBUtil.FEATURE_SERVICE_LAYER_IDS) {

			mapView.addLayer(new ArcGISFeatureLayer(
					GDBUtil.DEFAULT_FEATURE_SERVICE_URL + "/" + i,
					ArcGISFeatureLayer.MODE.ONDEMAND));
		}

		Envelope env = new Envelope(-122.514731, 37.762135, -122.433192,
				37.787237);
		mapView.setExtent(env);

		/**
		 * When the basemap is initialized the status will be true.
		 */
		mapView.setOnStatusChangedListener(new OnStatusChangedListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void onStatusChanged(final Object source, final STATUS status) {

				if (STATUS.INITIALIZED == status) {

					if (source instanceof MapView) {
						graphicsLayer = new GraphicsLayer();
						highlightGraphics = new GraphicsLayer();
						mapView.addLayer(graphicsLayer);
						mapView.addLayer(highlightGraphics);
					}
				}
				if (STATUS.LAYER_LOADED == status) {
					if (source instanceof ArcGISFeatureLayer) {
						GDBUtil.showProgress(OfflineEditorActivity.this, false);
					}
				}
			}
		});
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		this.menu = menu;
		getMenuInflater().inflate(R.menu.action, menu);
		editMenuItem = menu.findItem(R.id.edit);
		offlineMenuItem = menu.findItem(R.id.go_offline);
		onlineMenuItem = menu.findItem(R.id.go_online);

		return super.onCreateOptionsMenu(menu);
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {

		case R.id.go_offline:
			item.setVisible(false);
			try {
				new ConnectToServer().execute("downloadGdb").get(5,
						TimeUnit.SECONDS);
			} catch (Exception e) {
				System.out.println("you are out");
				e.printStackTrace();
			}
			editMenuItem.setVisible(true);
			onlineMenuItem.setVisible(true);
			return true;

		case R.id.go_online:
			item.setVisible(false);
			GDBUtil.goOnline(OfflineEditorActivity.this, mapView);
			offlineMenuItem.setVisible(true);
			editMenuItem.setVisible(false);
			return true;

		case R.id.edit:
			OfflineActions offlineActions = new OfflineActions(
					OfflineEditorActivity.this);
			startActionMode(offlineActions);
			showEditTemplatePicker();
			return true;

		default:
			break;
		}

		return super.onOptionsItemSelected(item);
	}

	/**
	 * Synchronizes the edits
	 */
	public void syncGdb() {
		new ConnectToServer().execute("syncGdb");
	}

	/**
	 * Removes the edits
	 */
	public void remove() {
		if (!vertexselected)
			points.remove(points.size() - 1); // remove last vertex
		else
			points.remove(insertingindex);
		midpointselected = false;
		vertexselected = false;
		editingstates.add(new EditingStates(points, midpointselected,
				vertexselected, insertingindex));
		refresh();

	}

	/**
	 * Shows the edit templates for all the feature layers in the map
	 */
	public void showEditTemplatePicker() {

		GDBUtil.showProgress(OfflineEditorActivity.this, true);
		clear();
		int layerCount = 0;
		for (Layer layer : mapView.getLayers()) {
			if (layer instanceof FeatureLayer) {
				layerCount++;
			}

		}
		if (layerCount > 0) {
			if (myListener == null) {
				myListener = new MyTouchListener(OfflineEditorActivity.this,
						mapView);
				mapView.setOnTouchListener(myListener);
			}
			if (getTemplatePicker() != null) {
				getTemplatePicker().showAtLocation(mapView, Gravity.BOTTOM, 0,
						0);
			} else {
				new TemplatePickerTask().execute();
			}
		} else {
			GDBUtil.showMessage(OfflineEditorActivity.this,
					"No Editable Local Feature Layers.");

		}
		GDBUtil.showProgress(OfflineEditorActivity.this, false);

	}

	/**
	 * Cancel the last change
	 */
	public void cancel() {
		midpointselected = false;
		vertexselected = false;
		refresh();

	}

	/**
	 * Revert back to the last state of the edit
	 */
	public void undo() {
		// only undo when more than one edit has been made
		if (editingstates.size() > 1) {
			editingstates.remove(editingstates.size() - 1);
			EditingStates state = editingstates.get(editingstates.size() - 1);
			points.clear();
			points.addAll(state.points1);
			Log.d(TAG, "# of points = " + points.size());
			midpointselected = state.midpointselected1;
			vertexselected = state.vertexselected1;
			insertingindex = state.insertingindex1;
			refresh();
		}
	}

	/**
	 * An instance of this class is created when a new point is to be
	 * added/moved/deleted. Hence we can describe this class as a container of
	 * points selected. Points, vertexes, or mid points.
	 */
	public class EditingStates {
		ArrayList<Point> points1 = new ArrayList<Point>();

		boolean midpointselected1 = false;

		boolean vertexselected1 = false;

		int insertingindex1;

		public EditingStates(ArrayList<Point> points, boolean midpointselected,
				boolean vertexselected, int insertingindex) {
			this.points1.addAll(points);
			this.midpointselected1 = midpointselected;
			this.vertexselected1 = vertexselected;
			this.insertingindex1 = insertingindex;
		}
	}

	/*
	 * MapView's touch listener
	 */
	public class MyTouchListener extends MapOnTouchListener {
		MapView map;

		Context context;

		boolean redrawCache = true;

		public MyTouchListener(Context context, MapView view) {
			super(context, view);
			this.context = context;
			map = view;
		}

		@Override
		public boolean onDragPointerMove(MotionEvent from, final MotionEvent to) {
			if (tp != null && !onlineData) {
				if (getTemplatePicker().getselectedTemplate() != null) {
					setEditingMode();
				}
			}
			return super.onDragPointerMove(from, to);
		}

		@Override
		public boolean onDragPointerUp(MotionEvent from, final MotionEvent to) {
			if (tp != null && !onlineData) {
				if (getTemplatePicker().getselectedTemplate() != null) {
					setEditingMode();
				}
			}
			return super.onDragPointerUp(from, to);
		}

		/**
		 * In this method we check if the point clicked on the map denotes a new
		 * point or means an existing vertex must be moved.
		 */
		@Override
		public boolean onSingleTap(final MotionEvent e) {
			if (tp != null && !onlineData) {

				Point point = map.toMapPoint(new Point(e.getX(), e.getY()));
				if (getTemplatePicker().getselectedTemplate() != null) {
					setEditingMode();

				}
				if (getTemplatePicker().getSelectedLayer() != null) {
					long[] featureIds = ((FeatureLayer) mapView
							.getLayerByID(getTemplatePicker()
									.getSelectedLayer().getID()))
							.getFeatureIDs(e.getX(), e.getY(), 25);
					if (featureIds.length > 0 && (!featureUpdate)) {
						featureUpdateId = featureIds[0];
						GeodatabaseFeature gdbFeatureSelected = (GeodatabaseFeature) ((FeatureLayer) mapView
								.getLayerByID(getTemplatePicker()
										.getSelectedLayer().getID()))
								.getFeature(featureIds[0]);
						if (editingmode == POLYLINE || editingmode == POLYGON) {
							if (gdbFeatureSelected.getGeometry().getType()
									.equals(Geometry.Type.POLYLINE)) {
								Polyline polyline = (Polyline) gdbFeatureSelected
										.getGeometry();
								for (int i = 0; i < polyline.getPointCount(); i++) {
									points.add(polyline.getPoint(i));
								}

								refresh();

								editingstates.add(new EditingStates(points,
										midpointselected, vertexselected,
										insertingindex));

							} else if (gdbFeatureSelected.getGeometry()
									.getType().equals(Geometry.Type.POLYGON)) {
								Polygon polygon = (Polygon) gdbFeatureSelected
										.getGeometry();
								for (int i = 0; i < polygon.getPointCount(); i++) {
									points.add(polygon.getPoint(i));
								}

								refresh();
								editingstates.add(new EditingStates(points,
										midpointselected, vertexselected,
										insertingindex));

							}
							featureUpdate = true;
						}
					} else {
						if (editingmode == POINT) {

							GeodatabaseFeature g;
							try {
								graphicsLayer.removeAll();
								// this needs to to be created from FeatureLayer
								// by
								// passing template
								g = ((GeodatabaseFeatureTable) ((FeatureLayer) mapView
										.getLayerByID(getTemplatePicker()
												.getSelectedLayer().getID()))
										.getFeatureTable())
										.createFeatureWithTemplate(
												getTemplatePicker()
														.getselectedTemplate(),
												point);
								Symbol symbol = ((FeatureLayer) mapView
										.getLayerByID(getTemplatePicker()
												.getSelectedLayer().getID()))
										.getRenderer().getSymbol(g);

								Graphic gr = new Graphic(g.getGeometry(),
										symbol, g.getAttributes());

								addedGraphicId = graphicsLayer.addGraphic(gr);
							} catch (TableException e1) {
								e1.printStackTrace();
							}

							points.clear();
						}
						if (!midpointselected && !vertexselected) {
							// check if user tries to select an existing point.
							int idx1 = getSelectedIndex(e.getX(), e.getY(),
									mpoints, map);
							if (idx1 != -1) {
								midpointselected = true;
								insertingindex = idx1;
							}

							if (!midpointselected) { // check vertices
								int idx2 = getSelectedIndex(e.getX(), e.getY(),
										points, map);
								if (idx2 != -1) {
									vertexselected = true;
									insertingindex = idx2;
								}

							}
							if (!midpointselected && !vertexselected) {
								// no match, add new vertex at the location
								points.add(point);
								editingstates.add(new EditingStates(points,
										midpointselected, vertexselected,
										insertingindex));
							}

						} else if (midpointselected || vertexselected) {
							int idx1 = getSelectedIndex(e.getX(), e.getY(),
									mpoints, map);
							int idx2 = getSelectedIndex(e.getX(), e.getY(),
									points, map);
							if (idx1 == -1 && idx2 == -1) {
								movePoint(point);
								editingstates.add(new EditingStates(points,
										midpointselected, vertexselected,
										insertingindex));
							} else {

								if (idx1 != -1) {
									insertingindex = idx1;
								}
								if (idx2 != -1) {
									insertingindex = idx2;
								}

								editingstates.add(new EditingStates(points,
										midpointselected, vertexselected,
										insertingindex));

							}
						} else {
							// an existing point has been selected previously.
							movePoint(point);
						}
						refresh();
						redrawCache = true;
						return true;
					}
				}
			}
			return true;
		}
	}

	/**
	 * The edits made are applied and hence saved on the server.
	 */
	public void save() {
		Graphic addedGraphic;
		MultiPath multipath;

		if (editingmode == POINT)
			try {
				addedGraphic = graphicsLayer.getGraphic(addedGraphicId);
				((FeatureLayer) mapView.getLayerByID(getTemplatePicker()
						.getSelectedLayer().getID())).getFeatureTable()
						.addFeature(addedGraphic);
				graphicsLayer.removeAll();
			} catch (TableException e1) {
				e1.printStackTrace();
			}
		else {
			if (editingmode == POLYLINE)
				multipath = new Polyline();
			else if (editingmode == POLYGON)
				multipath = new Polygon();
			else
				return;
			multipath.startPath(points.get(0));
			for (int i = 1; i < points.size(); i++) {
				multipath.lineTo(points.get(i));
			}

			// Simplify the geometry that is to be set on the graphics.
			// Note this call is local not made to the server.
			Geometry geom = GeometryEngine.simplify(multipath,
					mapView.getSpatialReference());
			if (featureUpdate) {
				try {
					GeodatabaseFeature g = ((GeodatabaseFeatureTable) ((FeatureLayer) mapView
							.getLayerByID(getTemplatePicker()
									.getSelectedLayer().getID()))
							.getFeatureTable()).createFeatureWithTemplate(
							getTemplatePicker().getselectedTemplate(), geom);
					Symbol symbol = ((FeatureLayer) mapView
							.getLayerByID(getTemplatePicker()
									.getSelectedLayer().getID())).getRenderer()
							.getSymbol(g);
					addedGraphic = new Graphic(geom, symbol, g.getAttributes());
					((FeatureLayer) mapView.getLayerByID(getTemplatePicker()
							.getSelectedLayer().getID())).getFeatureTable()
							.updateFeature(featureUpdateId, addedGraphic);
				} catch (TableException e) {
					e.printStackTrace();
				}
			} else {
				try {
					GeodatabaseFeature g = ((GeodatabaseFeatureTable) ((FeatureLayer) mapView
							.getLayerByID(getTemplatePicker()
									.getSelectedLayer().getID()))
							.getFeatureTable()).createFeatureWithTemplate(
							getTemplatePicker().getselectedTemplate(), geom);
					Symbol symbol = ((FeatureLayer) mapView
							.getLayerByID(getTemplatePicker()
									.getSelectedLayer().getID())).getRenderer()
							.getSymbol(g);
					addedGraphic = new Graphic(geom, symbol, g.getAttributes());
					((FeatureLayer) mapView.getLayerByID(getTemplatePicker()
							.getSelectedLayer().getID())).getFeatureTable()
							.addFeature(addedGraphic);
				} catch (TableException e) {
					e.printStackTrace();
				}
			}
		}
	}

	void movePoint(Point point) {

		if (midpointselected) {
			// Move mid-point to the new location and make it a vertex.
			points.add(insertingindex + 1, point);
			editingstates.add(new EditingStates(points, midpointselected,
					vertexselected, insertingindex));
		} else if (vertexselected) {
			ArrayList<Point> temp = new ArrayList<Point>();
			for (int i = 0; i < points.size(); i++) {
				if (i == insertingindex)
					temp.add(point);
				else
					temp.add(points.get(i));
			}
			points.clear();
			points.addAll(temp);
			editingstates.add(new EditingStates(points, midpointselected,
					vertexselected, insertingindex));
		}
		midpointselected = false; // back to the normal drawing mode.
		vertexselected = false;

	}

	void refresh() {

		if (editingmode != POINT) {
			if (graphicsLayerEditing != null && graphicsLayer != null) {
				graphicsLayerEditing.removeAll();
				graphicsLayer.removeAll();
			}

			drawPolyline();
			drawMidPoints();
			drawVertices();
		}
	}

	private void drawMidPoints() {
		int index;
		Graphic graphic;
		if (graphicsLayerEditing == null) {
			graphicsLayerEditing = new GraphicsLayer();
			mapView.addLayer(graphicsLayerEditing);
		}
		// draw mid-point
		if (points.size() > 1) {
			mpoints.clear();
			for (int i = 1; i < points.size(); i++) {
				Point p1 = points.get(i - 1);
				Point p2 = points.get(i);
				mpoints.add(new Point((p1.getX() + p2.getX()) / 2,
						(p1.getY() + p2.getY()) / 2));
			}
			if (editingmode == POLYGON) { // complete the circle
				Point p1 = points.get(0);
				Point p2 = points.get(points.size() - 1);
				mpoints.add(new Point((p1.getX() + p2.getX()) / 2,
						(p1.getY() + p2.getY()) / 2));
			}
			index = 0;
			for (Point pt : mpoints) {

				if (midpointselected && insertingindex == index)
					graphic = new Graphic(pt, new SimpleMarkerSymbol(Color.RED,
							20, SimpleMarkerSymbol.STYLE.CIRCLE));
				else
					graphic = new Graphic(pt, new SimpleMarkerSymbol(
							Color.GREEN, 15, SimpleMarkerSymbol.STYLE.CIRCLE));
				graphicsLayerEditing.addGraphic(graphic);
				index++;
			}
		}
	}

	private void drawVertices() {
		int index;
		// draw vertices
		index = 0;

		if (graphicsLayerEditing == null) {
			graphicsLayerEditing = new GraphicsLayer();
			mapView.addLayer(graphicsLayerEditing);
		}

		for (Point pt : points) {
			if (vertexselected && index == insertingindex) {
				Graphic graphic = new Graphic(pt, new SimpleMarkerSymbol(
						Color.RED, 20, SimpleMarkerSymbol.STYLE.CIRCLE));
				Log.d(TAG, "Add Graphic vertex");
				graphicsLayerEditing.addGraphic(graphic);
			} else if (index == points.size() - 1 && !midpointselected
					&& !vertexselected) {
				Graphic graphic = new Graphic(pt, new SimpleMarkerSymbol(
						Color.RED, 20, SimpleMarkerSymbol.STYLE.CIRCLE));

				int id = graphicsLayer.addGraphic(graphic);

				Log.d(TAG,
						"Add Graphic mid point" + pt.getX() + " " + pt.getY()
								+ " id = " + id);

			} else {
				Graphic graphic = new Graphic(pt, new SimpleMarkerSymbol(
						Color.BLACK, 20, SimpleMarkerSymbol.STYLE.CIRCLE));
				Log.d(TAG, "Add Graphic point");
				graphicsLayerEditing.addGraphic(graphic);
			}

			index++;
		}
	}

	private void drawPolyline() {

		if (graphicsLayerEditing == null) {
			graphicsLayerEditing = new GraphicsLayer();
			mapView.addLayer(graphicsLayerEditing);
		}
		if (points.size() <= 1)
			return;
		Graphic graphic;
		MultiPath multipath;
		if (editingmode == POLYLINE)
			multipath = new Polyline();
		else
			multipath = new Polygon();
		multipath.startPath(points.get(0));
		for (int i = 1; i < points.size(); i++) {
			multipath.lineTo(points.get(i));
		}
		Log.d(TAG, "DrawPolyline: Array coutn = " + points.size());
		if (editingmode == POLYLINE)
			graphic = new Graphic(multipath, new SimpleLineSymbol(Color.BLACK,
					4));
		else {
			SimpleFillSymbol simpleFillSymbol = new SimpleFillSymbol(
					Color.YELLOW);
			simpleFillSymbol.setAlpha(100);
			simpleFillSymbol.setOutline(new SimpleLineSymbol(Color.BLACK, 4));
			graphic = new Graphic(multipath, (simpleFillSymbol));
		}
		Log.d(TAG, "Add Graphic Line in DrawPolyline");
		graphicsLayerEditing.addGraphic(graphic);
	}

	public void clear() {
		if (graphicsLayer != null) {
			graphicsLayer.removeAll();
		}

		if (graphicsLayerEditing != null) {
			graphicsLayerEditing.removeAll();
		}
		if (highlightGraphics != null) {
			highlightGraphics.removeAll();
			mapView.getCallout().hide();

		}

		featureUpdate = false;
		points.clear();
		mpoints.clear();
		midpointselected = false;
		vertexselected = false;
		insertingindex = 0;
		editingstates.clear();

	}

	/**
	 * return index of point in array whose distance to touch point is minimum
	 * and less than 40.
	 */
	int getSelectedIndex(double x, double y, ArrayList<Point> points1,
			MapView map) {

		if (points1 == null || points1.size() == 0)
			return -1;

		int index = -1;
		double distSQ_Small = Double.MAX_VALUE;
		for (int i = 0; i < points1.size(); i++) {
			Point p = map.toScreenPoint(points1.get(i));
			double diffx = p.getX() - x;
			double diffy = p.getY() - y;
			double distSQ = diffx * diffx + diffy * diffy;
			if (distSQ < distSQ_Small) {
				index = i;
				distSQ_Small = distSQ;
			}
		}

		if (distSQ_Small < (40 * 40)) {
			return index;
		}
		return -1;

	}

	private void setEditingMode() {
		if (getTemplatePicker() != null) {
			if (getTemplatePicker().getSelectedLayer().getGeometryType()
					.equals(Geometry.Type.POINT)
					|| getTemplatePicker().getSelectedLayer().getGeometryType()
							.equals(Geometry.Type.MULTIPOINT)) {
				editingmode = POINT;
				template = getTemplatePicker().getselectedTemplate();
			} else if (getTemplatePicker().getSelectedLayer().getGeometryType()
					.equals(Geometry.Type.POLYLINE)) {
				editingmode = POLYLINE;
				template = getTemplatePicker().getselectedTemplate();
			} else if (getTemplatePicker().getSelectedLayer().getGeometryType()
					.equals(Geometry.Type.POLYGON)) {
				editingmode = POLYGON;
				template = getTemplatePicker().getselectedTemplate();
			}
		}
	}

	public MapView getMapView() {
		return mapView;
	}

	/**
	 * Connect to server to synchronize edits back or download features locally
	 */
	public class ConnectToServer extends AsyncTask<String, Void, Void> {

		@Override
		protected void onPreExecute() {
			progress = new ProgressDialog(OfflineEditorActivity.this);
			progress = ProgressDialog.show(OfflineEditorActivity.this, "",
					"Processing... Please wait...");
		}

		@Override
		protected Void doInBackground(String... params) {
			if (params[0].equals("syncGdb")) {
				GDBUtil.synchronize(OfflineEditorActivity.this);
			} else if (params[0].equals("downloadGdb")) {
				GDBUtil.downloadData(OfflineEditorActivity.this);
			}
			return null;
		}

	}

	/**
	 * This is responsible for retrieving the template types for the edits.
	 */
	public class TemplatePickerTask extends AsyncTask<Void, Void, Void> {

		ProgressDialog progressDialog;

		@Override
		protected Void doInBackground(Void... params) {

			setTemplatePicker(new TemplatePicker(OfflineEditorActivity.this,
					mapView));
			return null;
		}

		@Override
		protected void onPreExecute() {
			progressDialog = ProgressDialog
					.show(OfflineEditorActivity.this,
							"Loading Edit Templates",
							"Might take more time for layers with many templates",
							true);
			super.onPreExecute();
		}

		@Override
		protected void onPostExecute(Void result) {
			progressDialog.dismiss();
			getTemplatePicker().showAtLocation(mapView, Gravity.BOTTOM, 0, 0);

			super.onPostExecute(result);
		}

	}

	public TemplatePicker getTemplatePicker() {
		return tp;
	}

	public void setTemplatePicker(TemplatePicker tp) {
		this.tp = tp;
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
	}

	@Override
	protected void onPause() {
		super.onPause();
		mapView.pause();
	}

	@Override
	protected void onResume() {
		super.onResume();
		mapView.unpause();

	}
}
package com.esri.arcgis.android.samples.offlineeditor;

/* Copyright 2014 ESRI
 *
 * All rights reserved under the copyright laws of the United States
 * and applicable international laws, treaties, and conventions.
 *
 * You may freely redistribute and use this sample code, with or
 * without modification, provided you include the original copyright
 * notice and use restrictions.
 *
 * See the sample code usage restrictions document for further information.
 *
 */

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.util.Map;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import com.esri.android.map.FeatureLayer;
import com.esri.android.map.Layer;
import com.esri.android.map.MapView;
import com.esri.android.map.ags.ArcGISFeatureLayer;
import com.esri.android.map.ags.ArcGISLocalTiledLayer;
import com.esri.android.map.ags.ArcGISTiledMapServiceLayer;
import com.esri.core.ags.FeatureServiceInfo;
import com.esri.core.geodatabase.Geodatabase;
import com.esri.core.geodatabase.GeodatabaseFeatureTable;
import com.esri.core.geodatabase.GeodatabaseFeatureTableEditErrors;
import com.esri.core.map.CallbackListener;
import com.esri.core.tasks.geodatabase.GenerateGeodatabaseParameters;
import com.esri.core.tasks.geodatabase.GeodatabaseStatusCallback;
import com.esri.core.tasks.geodatabase.GeodatabaseStatusInfo;
import com.esri.core.tasks.geodatabase.GeodatabaseSyncTask;
import com.esri.core.tasks.geodatabase.SyncGeodatabaseParameters;

public class GDBUtil {

  static final String DEFAULT_FEATURE_SERVICE_URL = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer";

  static final String DEFAULT_BASEMAP_SERVICE_URL = "http://services.arcgisonline.com/ArcGIS/rest/services/ESRI_StreetMap_World_2D/MapServer";

  static final String DEFAULT_GDB_PATH = "/ArcGIS/samples/OfflineEditor/";

  static final String DEFAULT_BASEMAP_FILENAME = "/ArcGIS/samples/OfflineEditor/SanFrancisco.tpk";

  static final int[] FEATURE_SERVICE_LAYER_IDS = { 0, 1, 2 };

  protected static final String TAG = "GDBUtil";

  private static GeodatabaseSyncTask gdbTask;

  private static String gdbFileName = Environment.getExternalStorageDirectory().getPath() + DEFAULT_GDB_PATH + "offlinedata.geodatabase";

  private static String basemapFileName = Environment.getExternalStorageDirectory().getPath()
      + DEFAULT_BASEMAP_FILENAME;

  /**
   * Go back online
   */
  public static void goOnline(final OfflineEditorActivity activity, final MapView mapView) {

    if (hasInternet(activity) && !activity.onlineData) {

      DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
          switch (which) {
            case DialogInterface.BUTTON_POSITIVE:
            	
            	// get root geodatabase directory
            	File dir = new File(Environment.getExternalStorageDirectory().getPath() + DEFAULT_GDB_PATH);
            	// create filter based on geodatabase name
				File[] fileDelete = dir.listFiles(new FileFilter() {

					@Override
					public boolean accept(File pathname) {
						return (pathname.getName()
								.startsWith("offlinedata"));
					}

				});
				// delete all geodatabase files
            	for(File file : fileDelete){
            		if(!file.delete()){
            			Log.e(TAG, "Can't remove " + file.getAbsolutePath());
            		}
            	}

              finishGoingOnline(activity, mapView);
              
              break;

            case DialogInterface.BUTTON_NEGATIVE:
              
              finishGoingOnline(activity, mapView);
              
              break;

          }
        }
      };

      AlertDialog.Builder builder = new AlertDialog.Builder(activity);
      builder.setMessage("Do you want to delete your previously downloaded geodatabase?").setPositiveButton("Yes", dialogClickListener)
          .setNegativeButton("No", dialogClickListener).show();

    } else {
      showMessage(activity, "No Internet Connection! Please try again");
    }
  }
  
  private static void finishGoingOnline(final OfflineEditorActivity activity, final MapView mapView){
    showProgress(activity, true);

    for (Layer layer : mapView.getLayers()) {
      if (layer instanceof FeatureLayer || layer instanceof ArcGISLocalTiledLayer)
        mapView.removeLayer(layer);
    }

    mapView.addLayer(new ArcGISTiledMapServiceLayer(DEFAULT_BASEMAP_SERVICE_URL), 0);

    for (int i : GDBUtil.FEATURE_SERVICE_LAYER_IDS) {
      mapView.addLayer(new ArcGISFeatureLayer(DEFAULT_FEATURE_SERVICE_URL + "/" + i,
          ArcGISFeatureLayer.MODE.ONDEMAND));
    }
    activity.onlineData = true;
  }

  /**
   * Checks whether the device is connected to a network
   */
  public static boolean hasInternet(Activity a) {
    boolean hasConnectedWifi = false;
    boolean hasConnectedMobile = false;

    ConnectivityManager cm = (ConnectivityManager) a.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo[] netInfo = cm.getAllNetworkInfo();
    for (NetworkInfo ni : netInfo) {
      if (ni.getTypeName().equalsIgnoreCase("wifi"))
        if (ni.isConnected())
          hasConnectedWifi = true;
      if (ni.getTypeName().equalsIgnoreCase("mobile"))
        if (ni.isConnected())
          hasConnectedMobile = true;
    }
    return hasConnectedWifi || hasConnectedMobile;
  }

  /**
   * Download data into a geodatabase
   * 
   * @param activity
   */
  public static void downloadData(final OfflineEditorActivity activity) {
    Log.i(TAG, "downloadData");
    showProgress(activity, true);
    final MapView mapView = activity.getMapView();
    downloadGeodatabase(activity, mapView);
  }

  // Fetches the geodatabase and loads onto the mapview either locally or
  // downloading from the server
  private static void downloadGeodatabase(final OfflineEditorActivity activity, final MapView mapView) {

    // request and download geodatabase from the server
    if (!isGeoDatabaseLocal()) {

      gdbTask = new GeodatabaseSyncTask(DEFAULT_FEATURE_SERVICE_URL, null);

      gdbTask.fetchFeatureServiceInfo(new CallbackListener<FeatureServiceInfo>() {

        @Override
        public void onError(Throwable e) {
          Log.e(TAG, "", e);
          showMessage(activity, e.getMessage());
          showProgress(activity, false);
        }

        @Override
        public void onCallback(FeatureServiceInfo fsInfo) {

          if (fsInfo.isSyncEnabled()) {
            requestGdbFromServer(gdbTask, activity, mapView, fsInfo);
          }
        }
      });
    }

    // load the geodatabase from the device
    else {

      // add local layers from the geodatabase
      addLocalLayers(mapView, gdbFileName, activity);
      showMessage(activity, "Loaded GDB Locally...");
      OfflineEditorActivity.progress.dismiss();
      showProgress(activity, false);
    }

  }

  /**
   * Download the geodatabase from the server.
   */
  private static void requestGdbFromServer(GeodatabaseSyncTask geodatabaseSyncTask,
      final OfflineEditorActivity activity, final MapView mapView, FeatureServiceInfo fsInfo) {

    GenerateGeodatabaseParameters params = new GenerateGeodatabaseParameters(fsInfo, mapView.getExtent(),
        mapView.getSpatialReference(), null, true);
    params.setOutSpatialRef(mapView.getSpatialReference());

    // gdb complete callback
    CallbackListener<String> gdbResponseCallback = new CallbackListener<String>() {

      @Override
      public void onCallback(String path) {

        // add local layers from the geodatabase
        addLocalLayers(mapView, path, activity);

        showMessage(activity, "Data Available Offline!");
        OfflineEditorActivity.progress.dismiss();
        showProgress(activity, false);

      }

      @Override
      public void onError(Throwable e) {
        Log.e(TAG, "", e);
        showMessage(activity, e.getMessage());
        showProgress(activity, false);
      }

    };

    GeodatabaseStatusCallback statusCallback = new GeodatabaseStatusCallback() {

      @Override
      public void statusUpdated(GeodatabaseStatusInfo status) {
        if (!status.isDownloading()) {
          showMessage(activity, status.getStatus().toString());
        }

      }
    };

    // single method does it all!
    geodatabaseSyncTask.generateGeodatabase(params, gdbFileName, false, statusCallback, gdbResponseCallback);
    showMessage(activity, "Submitting gdb job...");
  }

  private static void addLocalLayers(final MapView mapView, String gdbPath, final OfflineEditorActivity activity) {
    // remove all the feature layers from map and add a feature
    // layer from the downloaded geodatabase
    for (Layer layer : mapView.getLayers()) {
      if (layer instanceof ArcGISFeatureLayer || layer instanceof ArcGISTiledMapServiceLayer)
        mapView.removeLayer(layer);
    }

    // Add local basemap layer if it exists
    if (isBasemapLocal()) {
      mapView.addLayer(new ArcGISLocalTiledLayer(basemapFileName), 0);
    } else {
      GDBUtil.showMessage(activity, "Local Basemap tpk doesn't exist");
    }

    // add layers from the geodatabase
    Geodatabase geodatabase;
    try {
      geodatabase = new Geodatabase(gdbPath);

      for (GeodatabaseFeatureTable gdbFeatureTable : geodatabase.getGeodatabaseTables()) {
        if (gdbFeatureTable.hasGeometry())
          mapView.addLayer(new FeatureLayer(gdbFeatureTable));
      }
      activity.setTemplatePicker(null);
      activity.onlineData = false;
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }

  }

  // upload and synchronize local geodatabase to the server
  static void synchronize(final OfflineEditorActivity activity) {
    showProgress(activity, true);

    gdbTask = new GeodatabaseSyncTask(DEFAULT_FEATURE_SERVICE_URL, null);
    gdbTask.fetchFeatureServiceInfo(new CallbackListener<FeatureServiceInfo>() {

      @Override
      public void onError(Throwable e) {

        Log.e(TAG, "", e);
        showMessage(activity, e.getMessage());
        showProgress(activity, false);
      }

      @Override
      public void onCallback(FeatureServiceInfo objs) {
        if (objs.isSyncEnabled()) {
          doSyncAllInOne(activity);
        }
      }
    });
  }

  /**
   * Synchronizing the edits to the Map working on both online/offline mode
   * 
   * @throws Exception
   */
  private static void doSyncAllInOne(final OfflineEditorActivity activity) {

    try {
      // create local geodatabase
      Geodatabase gdb = new Geodatabase(gdbFileName);

      // get sync parameters from geodatabase
      final SyncGeodatabaseParameters syncParams = gdb.getSyncParameters();

      CallbackListener<Map<Integer, GeodatabaseFeatureTableEditErrors>> syncResponseCallback = new CallbackListener<Map<Integer, GeodatabaseFeatureTableEditErrors>>() {

        @Override
        public void onCallback(Map<Integer, GeodatabaseFeatureTableEditErrors> objs) {
          showProgress(activity, false);
          if (objs != null) {
            if (objs.size() > 0) {

              showMessage(activity, "Sync Completed With Errors");
            } else {
              showMessage(activity, "Sync Completed Without Errors");
            }

          } else {
            showMessage(activity, "Sync Completed Without Errors");
          }
          OfflineEditorActivity.progress.dismiss();
        }

        @Override
        public void onError(Throwable e) {
          Log.e(TAG, "", e);
          showMessage(activity, e.getMessage());
          showProgress(activity, false);
        }

      };

      GeodatabaseStatusCallback statusCallback = new GeodatabaseStatusCallback() {

        @Override
        public void statusUpdated(GeodatabaseStatusInfo status) {

          showMessage(activity, status.getStatus().toString());
        }
      };

      // Performs Synchronization
      gdbTask.syncGeodatabase(syncParams, gdb, statusCallback, syncResponseCallback);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Checks whether the basemap is available locally
   */
  public static boolean isBasemapLocal() {
    File file = new File(basemapFileName);
    return file.exists();
  }

  /**
   * Checks whether the geodatabase is available locally
   */
  public static boolean isGeoDatabaseLocal() {
    File file = new File(gdbFileName);
    return file.exists();
  }

  static void showProgress(final OfflineEditorActivity activity, final boolean b) {
    activity.runOnUiThread(new Runnable() {

      @Override
      public void run() {
        activity.setProgressBarIndeterminateVisibility(b);
      }
    });
  }

  static void showMessage(final OfflineEditorActivity activity, final String message) {
    activity.runOnUiThread(new Runnable() {

      @Override
      public void run() {
        Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
      }
    });
  }

}
package com.esri.arcgis.android.samples.offlineeditor;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.view.Display;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.HorizontalScrollView;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.ScrollView;
import android.widget.TextView;

import com.esri.android.map.FeatureLayer;
import com.esri.android.map.Layer;
import com.esri.android.map.MapView;
import com.esri.core.geodatabase.GeodatabaseFeature;
import com.esri.core.geodatabase.GeodatabaseFeatureTable;
import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.Point;
import com.esri.core.geometry.Polygon;
import com.esri.core.geometry.Polyline;
import com.esri.core.map.FeatureTemplate;
import com.esri.core.map.FeatureType;
import com.esri.core.renderer.Renderer;
import com.esri.core.symbol.Symbol;
import com.esri.core.symbol.SymbolHelper;
import com.esri.core.table.TableException;

public class TemplatePicker extends PopupWindow {
  Context context;

  MapView map;

  private FeatureLayer selectedLayer;

  private FeatureTemplate selectedTemplate;

  private Symbol selectedSymbol;

  public TemplatePicker(Context ctx, MapView map) {
    this.context = ctx;
    this.map = map;
    setContentView(populateContentView());
    Display display = ((Activity) ctx).getWindowManager().getDefaultDisplay();
    // create an pont object to receive the size information
    android.graphics.Point size = new android.graphics.Point();
    // get the size in pixels
    display.getSize(size);
    // size.x = width
    setWidth(size.x);
    setHeight(300);
    setFocusable(true);
  }

  public View populateContentView() {

    ScrollView scrollTemplateGroup = new ScrollView(context);
    scrollTemplateGroup.setVerticalScrollBarEnabled(true);
    scrollTemplateGroup.setVisibility(View.VISIBLE);
    scrollTemplateGroup.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);

    LinearLayout templateGroup = new LinearLayout(context);
    templateGroup.setPadding(10, 10, 10, 10);
    templateGroup.setLayoutParams(new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT,
        android.view.ViewGroup.LayoutParams.MATCH_PARENT));
    templateGroup.setBackgroundResource(R.drawable.popupbg);
    templateGroup.setOrientation(LinearLayout.VERTICAL);

    for (Layer layer : map.getLayers()) {
      if (layer instanceof FeatureLayer) {
        RelativeLayout templateLayout = new RelativeLayout(context);
        templateLayout.setLayoutParams(new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT,
            android.view.ViewGroup.LayoutParams.MATCH_PARENT));

        TextView layerName = new TextView(context);
        layerName.setPadding(10, 10, 10, 10);
        layerName.setText(layer.getName());
        layerName.setTextColor(Color.MAGENTA);
        layerName.setId(1);

        RelativeLayout.LayoutParams layerTemplateParams = new RelativeLayout.LayoutParams(
            android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
        layerTemplateParams.addRule(RelativeLayout.BELOW, layerName.getId());
        HorizontalScrollView scrollTemplateAndType = new HorizontalScrollView(context);
        scrollTemplateAndType.setPadding(5, 5, 5, 5);
        LinearLayout layerTemplate = new LinearLayout(context);
        layerTemplate.setBackgroundColor(Color.WHITE);
        layerTemplate.setLayoutParams(new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
            android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
        layerTemplate.setId(2);
        String typeIdField = ((GeodatabaseFeatureTable) ((FeatureLayer) layer).getFeatureTable()).getTypeIdField();

        if (typeIdField.equals("")) {
          List<FeatureTemplate> featureTemp = ((GeodatabaseFeatureTable) ((FeatureLayer) layer).getFeatureTable())
              .getFeatureTemplates();

          for (FeatureTemplate featureTemplate : featureTemp) {
            GeodatabaseFeature g;
            try {
              g = ((GeodatabaseFeatureTable) ((FeatureLayer) layer).getFeatureTable()).createFeatureWithTemplate(
                  featureTemplate, null);
              Renderer renderer = ((FeatureLayer) layer).getRenderer();
              Symbol symbol = renderer.getSymbol(g);

              Bitmap bitmap = createBitmapfromSymbol(symbol, (FeatureLayer) layer);

              populateTemplateView(layerTemplate, bitmap, featureTemplate, (FeatureLayer) layer, symbol);
            } catch (TableException e) {

              e.printStackTrace();
            }

          }

        } else {
          List<FeatureType> featureTypes = ((GeodatabaseFeatureTable) ((FeatureLayer) layer).getFeatureTable())
              .getFeatureTypes();

          for (FeatureType featureType : featureTypes) {

            FeatureTemplate[] templates = featureType.getTemplates();
            for (FeatureTemplate featureTemplate : templates) {
              GeodatabaseFeature g;
              try {
                g = ((GeodatabaseFeatureTable) ((FeatureLayer) layer).getFeatureTable()).createFeatureWithTemplate(
                    featureTemplate, null);
                Renderer renderer = ((FeatureLayer) layer).getRenderer();
                Symbol symbol = renderer.getSymbol(g);
                Bitmap bitmap = createBitmapfromSymbol(symbol, (FeatureLayer) layer);

                populateTemplateView(layerTemplate, bitmap, featureTemplate, (FeatureLayer) layer, symbol);
              } catch (TableException e) {
                e.printStackTrace();
              }

            }

          }
        }

        templateLayout.addView(layerName);
        scrollTemplateAndType.addView(layerTemplate);
        templateLayout.addView(scrollTemplateAndType, layerTemplateParams);
        templateGroup.addView(templateLayout);
      }

    }
    scrollTemplateGroup.addView(templateGroup);

    return scrollTemplateGroup;
  }

  private Bitmap createBitmapfromSymbol(Symbol symbol, FeatureLayer layer) {
    Bitmap bitmap = null;
    if (layer.getGeometryType().equals(Geometry.Type.POINT)) {
      Point pt = new Point(20, 20);
      bitmap = SymbolHelper.getLegendImage(symbol, pt, 50, 50, Color.WHITE);
    } else if (layer.getGeometryType().equals(Geometry.Type.POLYLINE)) {
      Polyline polyline = new Polyline();
      polyline.startPath(0, 0);
      polyline.lineTo(40, 40);
      bitmap = SymbolHelper.getLegendImage(symbol, polyline, 50, 50, Color.WHITE);
    } else if (layer.getGeometryType().equals(Geometry.Type.POLYGON)) {
      Polygon polygon = new Polygon();
      polygon.startPath(0, 0);
      polygon.lineTo(40, 0);
      polygon.lineTo(40, 40);
      polygon.lineTo(0, 40);
      polygon.lineTo(0, 0);
      bitmap = SymbolHelper.getLegendImage(symbol, polygon, 50, 50, Color.WHITE);
    }
    return bitmap;
  }

  private void populateTemplateView(LinearLayout layerTemplate, Bitmap bitmap, final FeatureTemplate featureTemplate,
      final FeatureLayer flayer, final Symbol symbol) {
    LinearLayout templateAndType = new LinearLayout(context);
    templateAndType.setLayoutParams(new LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
        android.view.ViewGroup.LayoutParams.WRAP_CONTENT));
    templateAndType.setOrientation(LinearLayout.VERTICAL);
    templateAndType.setPadding(10, 10, 10, 10);

    final ImageButton template = new ImageButton(context);
    template.setBackgroundColor(Color.WHITE);

    template.setImageBitmap(bitmap);
    template.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
        selectedLayer = flayer;
        selectedTemplate = featureTemplate;
        selectedSymbol = symbol;
        dismiss();

      }
    });

    TextView templateType = new TextView(context);
    templateType.setText(featureTemplate.getName());
    templateType.setPadding(5, 5, 5, 5);
    templateAndType.addView(template);
    templateAndType.addView(templateType);

    LinearLayout.LayoutParams templateParams = new LinearLayout.LayoutParams(
        android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
    templateParams.setMargins(20, 20, 20, 20);
    layerTemplate.addView(templateAndType, templateParams);
  }

  public FeatureLayer getSelectedLayer() {
    return selectedLayer;
  }

  public FeatureTemplate getselectedTemplate() {

    return selectedTemplate;
  }

  public void clearSelection() {
    this.selectedTemplate = null;
    this.selectedLayer = null;
  }

  public Symbol getSelectedSymbol() {
    return selectedSymbol;
  }

}
Feedback on this topic?