Popup UI Customization

The purpose of this sample is to show how to customize the UI of a pop-up. The layout and style of a pop-up can be changed through XML files and extending some built-in classes of the pop-up API.

Features

  • Popup API
  • MapViewHelper

Sample Design

This sample shows how to customize the UI of a pop-up and two ways of creating pop-ups. The simplest way is to utilize a helper class from ArcGIS Android Application Tookit called MapViewHelper to create pop-ups with default UI. The MapViewHelper class will iterate through and query each layer in the MapView. A pop-up will be created for each feature in the query result and will be added to a PopupContainer. The user-defined PopupCreateListener is called after the pop-ups are created. You can use the logic in the PopupCreateListener to display the pop-ups.

Another approach is more complicated but more powerful. This approach allows users to customize the UI of a pop-up. In this sample a class called LayerQueryTask demonstrates this approach. LayerQueryTask loops through each layer and queries the MapView in an asynchronous task. The asynchronous task will create pop-ups for the query result. Before a pop-up is created, a new PopupLayoutInfo will be populated from XML file and be passed to Layer.createPopup() as a parameter. Then the four basic views such as title, attribute, media and attachment view will be modified by instantiating the sub-classes of the built-in classes of the views. The style of the views then can be customized through these sub-classes and XML files.

Pop-ups will be display in a fragment called PopupFragment. This fragment also provides menu to edit or delete a feature based on the edit properties of the feature.

Sample Requirements

The Popup samples depend on the Application Tookit for ArcGIS Android and the Andriod Support Library. Instructions for setting that it up prior to running the app is detailed below.

Sample Code

/* 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.
 *
 */

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

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.Toast;

import com.esri.android.map.MapView;
import com.esri.android.map.ags.ArcGISFeatureLayer;
import com.esri.android.map.event.OnLongPressListener;
import com.esri.android.map.event.OnSingleTapListener;
import com.esri.android.map.event.OnStatusChangedListener;
import com.esri.android.map.popup.Popup;
import com.esri.android.map.popup.PopupContainer;
import com.esri.android.toolkit.map.MapViewHelper;
import com.esri.android.toolkit.map.PopupCreateListener;
import com.esri.arcgis.android.samples.PopupUICustomization.PopupFragment.OnEditListener;
import com.esri.core.map.CallbackListener;
import com.esri.core.map.FeatureEditResult;
import com.esri.core.map.Graphic;

/*
 * This sample shows how to customize the UI of a pop-up. The layout and style of a pop-up can be 
 * changed through XML and extending some built-in classes of the pop-up API.
 * 
 * Single-tap will bring up pop-ups with customized UI. Long-press will display pop-ups of default UI for comparison. 
 * A helper class from Application Toolkit called MapViewHelper is used to create pop-ups with default UI. 
 */

public class PopupUICustomizationActivity extends FragmentActivity implements OnEditListener {
	
	private MapView mMapView;
	private MapViewHelper mMapViewHelper;
	private PopupFragment mPopupFragment;
	
	private final static String URL = "http://www.arcgis.com/home/item.html?id=c93081d0ca924317bebf2b362d3bc8de";
	
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Display a web map.
    mMapView = new MapView(this, URL, "", "");
    
    setContentView(mMapView );
    
    mMapView.setOnStatusChangedListener(new OnStatusChangedListener() {
     
      private static final long serialVersionUID = 1L;

      @Override
      public void onStatusChanged(Object source, STATUS status) {
        if ((status == STATUS.INITIALIZED) && (source == mMapView)) {
          // Create a MapViewHelper object once the map view has been initialized.
          mMapViewHelper = new MapViewHelper(mMapView);
        }
      }
    });
    
    // Query all the layers within the mapview and customized the UI of the pop-ups
    // in async tasks.
    mMapView.setOnSingleTapListener(new OnSingleTapListener() {
      
      private static final long serialVersionUID = 1L;
      
      @Override
      public void onSingleTap(float x, float y) {
        if (!mMapView.isLoaded())
          return;
        
        mPopupFragment = new PopupFragment(mMapView);
        LayerQueryTask queryTask = new LayerQueryTask(mMapView, mPopupFragment);
        queryTask.queryLayers(x, y, 20, true);
      }
    });
    
    // Query all the layers and display pop-up with default UI using MapViewHelper
    // using a helper class of Application Toolkit.
    mMapView.setOnLongPressListener(new OnLongPressListener() {
      
      private static final long serialVersionUID = 1L;

      @Override
      public boolean onLongPress(float x, float y) {
        
        mPopupFragment = null;
        // The helper class from Application Toolkit will loop through and query each layer in the mapview.
        // A pop-up will be created for each feature in the query result and will be added to a PopupContainer.
        // The user-define PopupCreateListerner will be called when a pop-up is created.
        // User can put their logic in the PopupCreateListerner to display the pop-ups.
        mMapViewHelper.createPopup(x, y, new SimplePopupCreateListener());
        return true;
      }
    });
  }
  
  @Override
  public void onDelete(ArcGISFeatureLayer featureLayer, Popup popup) {
    // Commit deletion to server
    Graphic graphic = (Graphic)popup.getFeature();
    if ((graphic != null) && (featureLayer != null)) {
      featureLayer.applyEdits(null, new Graphic[]{graphic}, null, 
          new EditCallbackListener(this, featureLayer, popup, true, "Deleting feature"));
    }
    
    // Dismiss pop-up
    this.getSupportFragmentManager().popBackStack();
  }
  
  @Override
  public void onEdit(ArcGISFeatureLayer featureLayer, Popup popup) {
    // Set pop-up into editing mode
    popup.setEditMode(true);
    // refresh menu items
    this.invalidateOptionsMenu();
  }
  
  @Override
  public void onSave(ArcGISFeatureLayer featureLayer, Popup popup) {
    // Commit edits to server
    Graphic graphic = (Graphic)popup.getFeature();
    if ((graphic != null) && (featureLayer != null)) {
      Map<String, Object> attributes = graphic.getAttributes();
      Map<String, Object> updatedAttrs = popup.getUpdatedAttributes();
      for (Entry<String, Object> entry : updatedAttrs.entrySet()) {
        attributes.put(entry.getKey(), entry.getValue());
      }
      Graphic newGraphic = new Graphic(graphic.getGeometry(), null, attributes);
      featureLayer.applyEdits(null, null, new Graphic[]{newGraphic}, 
          new EditCallbackListener(this, featureLayer, popup, true, "Saving feature"));
    }
    
    // Dismiss pop-up
    this.getSupportFragmentManager().popBackStack();
  }
  
  // TODO: add your logic to handler click event of image buttons shown in a pop-up
  public void addFavorite(View view) {
    
  }
  
  public void findDirection(View view) {
    
  }
  
  public void share(View view) {
    
  }
  
	
	@Override
	protected void onPause() {
		super.onPause();
		mMapView.pause();
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		mMapView.unpause();
	}

	 // Handle callback on committing edits to server
  private class EditCallbackListener implements CallbackListener<FeatureEditResult[][]> {
    private String mOperation = "Operation ";
    private ArcGISFeatureLayer mFeatureLayer = null;
    private boolean mExistingFeature = true;
    private Popup mPopup =null;
    private Context mContext;
    // constant defined for edit result index
    private final static int ADDITION_INDEX = 0;
    private final static int DELETION_INDEX = 1;
    private final static int UPDATE_INDEX = 2;
    
    public EditCallbackListener(Context context, ArcGISFeatureLayer featureLayer, Popup popup, 
        boolean existingFeature, String msg) {
      this.mOperation = msg;
      this.mFeatureLayer = featureLayer;
      this.mExistingFeature = existingFeature;
      this.mPopup = popup;
      this.mContext = context;
    }
    
    @Override
    public void onCallback(FeatureEditResult[][] editResults) {
      if ((mFeatureLayer == null) || (!mFeatureLayer.isInitialized()) 
          || (!mFeatureLayer.isEditable()))
        return;
      
      runOnUiThread(new Runnable() {
        
        @Override
        public void run() {
          Toast.makeText(mContext, mOperation + " succeeded!", Toast.LENGTH_SHORT).show();
        }
      });   
      
      // Similar to featureLayer.applyEdits editResults[0] holds result of feature addition, 
      // while editResults[1] for feature deletion and editResults[2] for feature update
      FeatureEditResult[] deletion = editResults[DELETION_INDEX];
      if ((deletion == null) || (deletion.length <= 0)) {
        // Save attachments to the server if newly added attachments exist.
        // Retrieve object id of the feature
        long oid; 
        FeatureEditResult featureEdit;
        if (mExistingFeature) {
          // The first item in the update edits
          featureEdit = editResults[UPDATE_INDEX][0];
        }  else {
          // The first item in the addition edits
          featureEdit = editResults[ADDITION_INDEX][0];
        }
        oid = featureEdit.getObjectId();
        // prepare oid as int for FeatureLayer
        int objectID = (int) oid;
        
        // Get newly added attachments
        List<File> attachments = mPopup.getAddedAttachments();
        if ((attachments != null) && (attachments.size() > 0)) {
          for (File attachment : attachments) {
            // Save newly added attachment based on the object id of the feature.
            mFeatureLayer.addAttachment(objectID, attachment, new CallbackListener<FeatureEditResult>() {
              @Override
              public void onError(Throwable e) {
                // Failed to save new attachments.
                runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    Toast.makeText(mContext, "Adding attachment failed!", Toast.LENGTH_SHORT).show();
                  }
                });
              }
              
              @Override
              public void onCallback(FeatureEditResult arg0) {
                // New attachments have been saved.
                runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    Toast.makeText(mContext, "Adding attachment succeeded!.", Toast.LENGTH_SHORT).show();
                  }
                });
              }
            });
          }
        }
        
        // Delete attachments if some attachments have been mark as delete.
        // Get ids of attachments which are marked as delete.
        List<Integer> attachmentIDs = mPopup.getDeletedAttachmentIDs();
        if ((attachmentIDs != null) && (attachmentIDs.size() > 0)) {
          int[] ids = new int[attachmentIDs.size()];
          for (int i = 0; i < attachmentIDs.size(); i++) {
            ids[i] = attachmentIDs.get(i);
          }
          // Delete attachments
          mFeatureLayer.deleteAttachments(objectID, ids, new CallbackListener<FeatureEditResult[]>() {
            @Override
            public void onError(Throwable e) {
              // Failed to delete attachments
              runOnUiThread(new Runnable() {
                @Override
                public void run() {
                  Toast.makeText(mContext, "Deleting attachment failed!", Toast.LENGTH_SHORT).show();
                }
              });
            }
            
            @Override
            public void onCallback(FeatureEditResult[] objs) {
              // Attachments have been removed.
              runOnUiThread(new Runnable() {
                @Override
                public void run() {
                  Toast.makeText(mContext, "Deleting attachment succeeded!", Toast.LENGTH_SHORT).show();
                }
              });
            }
          });
        }
      
      }
    }

    @Override
    public void onError(Throwable e) {
      runOnUiThread(new Runnable() {
        
        @Override
        public void run() {
          Toast.makeText(mContext, mOperation + " failed!", Toast.LENGTH_SHORT).show();
        }
      });   
    }
    
  }
  
	// Display pop-up fragment on a UI thread.
  private class SimplePopupCreateListener implements PopupCreateListener {
  
     @Override
     public void onResult(final PopupContainer container) {
       if ((container != null) && (container.getPopupCount() > 0)) {
          ((Activity) PopupUICustomizationActivity.this).runOnUiThread(new Runnable() {
  
             @Override
             public void run() {
               if (mPopupFragment == null ) {
                 mPopupFragment = new PopupFragment(mMapView, container);
                 mPopupFragment.show();
               }
             }
         });
       }
     }
     
   }

}
/* 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.
 *
 */

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

import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.view.ViewGroup;

import com.esri.android.map.GroupLayer;
import com.esri.android.map.Layer;
import com.esri.android.map.MapView;
import com.esri.android.map.ags.ArcGISDynamicMapServiceLayer;
import com.esri.android.map.ags.ArcGISFeatureLayer;
import com.esri.android.map.ags.ArcGISLayerInfo;
import com.esri.android.map.ags.ArcGISTiledMapServiceLayer;
import com.esri.android.map.popup.ArcGISAttributeView;
import com.esri.android.map.popup.ArcGISTitleView;
import com.esri.android.map.popup.ArcGISValueFormat;
import com.esri.android.map.popup.Popup;
import com.esri.android.map.popup.PopupLayout;
import com.esri.android.map.popup.PopupLayoutInfo;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Geometry;
import com.esri.core.geometry.Point;
import com.esri.core.geometry.SpatialReference;
import com.esri.core.map.Feature;
import com.esri.core.map.FeatureResult;
import com.esri.core.map.Graphic;
import com.esri.core.map.popup.PopupInfo;
import com.esri.core.tasks.query.QueryParameters;
import com.esri.core.tasks.query.QueryTask;

/*
 * This class will loop through and query each layer in the mapview. The query result will
 * be displayed in pop-ups. The layout and style of the pop-ups will be customized through
 * XML and sub-classes of some built-in classes of the pop-up API. 
 */
public class LayerQueryTask {
	private MapView mMapView;
	private PopupFragment mPopupFragment;
	private AtomicInteger mCount;
	private boolean mShowGeometryInfo;

	public LayerQueryTask(MapView map, PopupFragment popupFragment) {
		this.mMapView = map;
		this.mPopupFragment = popupFragment;
	}

	// Query each layer in the mapview in async tasks then display result in
	// customized pop-ups.
	public void queryLayers(float x, float y, int tolerance,
			boolean showGeometryInfo) {
		if ((mMapView == null) || (!mMapView.isLoaded())
				|| (mPopupFragment == null))
			return;

		this.mShowGeometryInfo = showGeometryInfo;

		// Loop through each layer in the mapview
		Envelope env = new Envelope(mMapView.toMapPoint(x, y), tolerance
				* mMapView.getResolution(), tolerance
				* mMapView.getResolution());
		Layer[] layers = mMapView.getLayers();
		mCount = new AtomicInteger();
		for (Layer layer : layers) {
			// If the layer has not been initialized or is invisible, do
			// nothing.
			if ((!layer.isInitialized()) || (!layer.isVisible()))
				continue;

			if (layer instanceof GroupLayer) {
				Layer[] sublayers = ((GroupLayer) layer).getLayers();
				if (sublayers != null) {
					for (Layer flayer : sublayers) {
						ArcGISFeatureLayer featureLayer = (ArcGISFeatureLayer) flayer;
						if (featureLayer.getPopupInfo() != null) {
							// Query feature layer which is associated with a
							// pop-up definition.
							mCount.incrementAndGet();
							new RunQueryFeatureLayerTask(x, y, tolerance)
									.execute(featureLayer);
						}
					}
				}
			} else if (layer instanceof ArcGISFeatureLayer) {
				// Query feature layer and display pop-ups
				ArcGISFeatureLayer featureLayer = (ArcGISFeatureLayer) layer;
				if (featureLayer.getPopupInfo() != null) {
					// Query feature layer which is associated with a popup
					// definition.
					mCount.incrementAndGet();
					new RunQueryFeatureLayerTask(x, y, tolerance)
							.execute(featureLayer);
				}
			} else if ((layer instanceof ArcGISDynamicMapServiceLayer)
					|| (layer instanceof ArcGISTiledMapServiceLayer)) {
				// Query dynamic map service layer and display pop-ups.
				ArcGISLayerInfo[] layerinfos;
				// Retrieve layer info for each sub-layer of the dynamic map
				// service layer.
				if (layer instanceof ArcGISDynamicMapServiceLayer)
					layerinfos = ((ArcGISDynamicMapServiceLayer) layer)
							.getAllLayers();
				else
					layerinfos = ((ArcGISTiledMapServiceLayer) layer)
							.getAllLayers();

				if (layerinfos == null)
					continue;

				// Loop through each sub-layer
				for (ArcGISLayerInfo layerInfo : layerinfos) {
					// Obtain PopupInfo for sub-layer.
					int subLayerId = layerInfo.getId();
					// Check if a sub-layer is visible by checking its
					// visibility and tracing back to its parent.
					// A sub-layer's visibility depends on the visibility of
					// itself and its parent. When a sub-layer
					// is visible but its parent is invisible, its visibility is
					// false. So need to track back to its parent.
					ArcGISLayerInfo info = layerInfo;
					// If a sub-layer is visible then get hold of its parent and
					// check the parent's visibility.
					// Continue till the current sub-layer is invisible or
					// reaches the root.
					while ((info != null) && (info.isVisible()))
						info = info.getParentLayer();
					// If the current sub-layer is invisible skip it.
					if ((info != null) && (!info.isVisible()))
						continue;

					// Check if the sub-layer is within the scale range
					double maxScale = layerInfo.getMaxScale();
					double minScale = layerInfo.getMinScale();

					if (((maxScale == 0) || (mMapView.getScale() > maxScale))
							&& ((minScale == 0) || (mMapView.getScale() < minScale))) {
						// Query sub-layer which is associated with a pop-up
						// definition and is visible and in scale range.
						String url = layer.getQueryUrl(subLayerId);
						if ((url == null) || (url.length() < 1))
							url = layer.getUrl() + "/" + subLayerId;
						mCount.incrementAndGet();
						new RunQueryDynamicLayerTask(env, layer,
								layerInfo.getId(), layer.getSpatialReference())
								.execute(url);
					}
				}
			}
		}

	}

	// Create a customized layout for a pop-up.
	private PopupLayoutInfo createPopupLayout(Context context) {

		PopupLayoutInfo layoutInfo = new PopupLayoutInfo();
		// inflate the layout from xml
		ViewGroup xmlLayout = (ViewGroup) ((Activity) context)
				.getLayoutInflater().inflate(R.layout.popup_layout, null);
		// set the xml layout on the pop-up layout
		layoutInfo.setLayout(xmlLayout);

		// Set the place holders for the four basic views in a pop-up.
		layoutInfo.setTitleViewPlaceHolder((ViewGroup) xmlLayout
				.findViewById(R.id.title_view_placeholder));
		layoutInfo.setAttributesViewPlaceHolder((ViewGroup) xmlLayout
				.findViewById(R.id.attribute_view_placeholder));
		layoutInfo.setMediaViewPlaceHolder((ViewGroup) xmlLayout
				.findViewById(R.id.media_view_placeholder));
		layoutInfo.setAttachmentsViewPlaceHolder((ViewGroup) xmlLayout
				.findViewById(R.id.attachment_view_placeholder));

		return layoutInfo;
	}

	// Customize the four basic views of a pop-up via xml and sub-classes.
	private void createViews(Popup popup) {
		if ((popup == null) || (popup.getLayout() == null))
			return;

		PopupLayout popupLayout = popup.getLayout();
		Context context = mMapView.getContext();

		// Create a view for the title
		popupLayout.setTitleView(new MyTitleView(context, popup, mMapView));

		// Create a view for attributes
		popupLayout.setAttributesView(new ArcGISAttributeView(context, popup));
		ArcGISAttributeView attributeView = (ArcGISAttributeView) popupLayout
				.getAttributesView();
		// Create an adapter for the attributes in read-only mode
		attributeView.setReadOnlyAdapter(new MyReadOnlyAttributesAdapter(
				context, popup));
		// Create an adapter for the attributes in edit mode
		attributeView
				.setEditAdapter(new MyEditAttributesAdapter(context, popup));

		// Change the view for the attachments
		if ((popup.getPopupInfo() != null)
				&& (popup.getPopupInfo().isShowAttachments())) {
			popupLayout
					.setAttachmentsView(new MyAttachmentsView(context, popup));
		}

		// Change the view for the media
		popupLayout.setMediaView(new MyMediaView(context, popup));
	}

	// Compose geometry info for a feature based on its geometry type.
	private String getGeometryInfo(Graphic graphic,
			ArcGISValueFormat valueFormat) {
		Geometry geometry = graphic.getGeometry();
		String geometryInfo = null;
		if (geometry == null) {
			geometryInfo = "Missing location";
		} else {
			String unit = mMapView.getSpatialReference().getUnit()
					.getDisplayName();
			switch (graphic.getGeometry().getType()) {
			case ENVELOPE:
			case POLYGON:
			case MULTIPOINT:
				geometryInfo = "Area: "
						+ valueFormat.formatValue(new Double(geometry
								.calculateArea2D())) + " Square " + unit + "s";
				break;
			case LINE:
			case POLYLINE:
				geometryInfo = "Distance: "
						+ valueFormat.formatValue(new Double(geometry
								.calculateLength2D())) + " " + unit + "s";
				break;
			case POINT:
			default:
				geometryInfo = "X: "
						+ valueFormat.formatValue(new Double(((Point) geometry)
								.getX()))
						+ ", Y:"
						+ valueFormat.formatValue(new Double(((Point) geometry)
								.getY()));
			}
		}

		return geometryInfo;
	}

	// Query dynamic map service layer by QueryTask
	private class RunQueryDynamicLayerTask extends
			AsyncTask<String, Void, FeatureResult> {

		private Envelope env;
		private SpatialReference sr;
		private Layer layer;
		private int subLayerId;

		public RunQueryDynamicLayerTask(Envelope env, Layer layer,
				int subLayerId, SpatialReference sr) {
			super();
			this.env = env;
			this.sr = sr;
			this.layer = layer;
			this.subLayerId = subLayerId;
		}

		@Override
		protected FeatureResult doInBackground(String... urls) {

			for (String url : urls) {
				PopupInfo popupInfo = layer.getPopupInfo(subLayerId);
				// Skip sub-layer which is without a pop-up definition.
				if (popupInfo == null)
					continue;

				// Retrieve graphics within the envelope.
				QueryParameters queryParams = new QueryParameters();
				queryParams.setInSpatialReference(sr);
				queryParams.setOutSpatialReference(sr);
				queryParams.setGeometry(env);
				queryParams.setMaxFeatures(10);
				queryParams.setOutFields(new String[] { "*" });

				try {
					QueryTask queryTask;
					queryTask = new QueryTask(url, layer.getCredentials());

					FeatureResult results = queryTask.execute(queryParams);
					return results;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			return null;
		}

		@Override
		protected void onPostExecute(final FeatureResult result) {
			// Validate parameter.
			mCount.decrementAndGet();
			if (result == null)
				return;

			// iterate through results
			for (Object element : result) {
				// if object is a feature cast to feature
				if (element instanceof Feature) {
					Feature feature = (Feature) element;
					// convert feature to graphic
					Graphic graphic = new Graphic(feature.getGeometry(), null,
							feature.getAttributes());
					PopupLayoutInfo layout = createPopupLayout(mMapView
							.getContext());
					Popup popup = layer.createPopup(mMapView, subLayerId,
							graphic, layout, false);
					createViews(popup);

					if (mShowGeometryInfo) {
						ArcGISValueFormat valueFormat = new ArcGISValueFormat(
								popup);
						ArcGISTitleView titleView = (ArcGISTitleView) popup
								.getLayout().getTitleView();
						titleView.setGeometryInfo(getGeometryInfo(graphic,
								valueFormat));
					}

					mPopupFragment.addPopup(popup);
					mPopupFragment.show();
				}
			}

		}

	}

	// Query feature layer by hit-test
	private class RunQueryFeatureLayerTask extends
			AsyncTask<ArcGISFeatureLayer, Void, Graphic[]> {

		private int tolerance;
		private float x;
		private float y;
		private ArcGISFeatureLayer featureLayer;

		public RunQueryFeatureLayerTask(float x, float y, int tolerance) {
			super();
			this.x = x;
			this.y = y;
			this.tolerance = tolerance;
		}

		@Override
		protected Graphic[] doInBackground(ArcGISFeatureLayer... params) {
			for (ArcGISFeatureLayer agsFeatureLayer : params) {
				this.featureLayer = agsFeatureLayer;
				// Retrieve graphic ids near the point.
				int[] ids = agsFeatureLayer.getGraphicIDs(x, y, tolerance);
				if ((ids != null) && (ids.length > 0)) {
					ArrayList<Graphic> graphics = new ArrayList<Graphic>();
					for (int id : ids) {
						// Obtain graphic based on the id.
						Graphic g = agsFeatureLayer.getGraphic(id);
						if (g == null)
							continue;
						graphics.add(g);
					}
					// Return an array of graphics near the point.
					return graphics.toArray(new Graphic[0]);
				}
			}

			return null;
		}

		@Override
		protected void onPostExecute(Graphic[] graphics) {
			mCount.decrementAndGet();
			// Validate parameter.
			if (graphics == null || graphics.length == 0)
				return;

			PopupInfo popupInfo = featureLayer.getPopupInfo();
			if (popupInfo == null)
				return;

			for (Graphic gr : graphics) {
				PopupLayoutInfo layout = createPopupLayout(mMapView
						.getContext());
				Popup popup = featureLayer.createPopup(mMapView, 0, gr, layout,
						false);
				createViews(popup);

				if (mShowGeometryInfo) {
					ArcGISValueFormat valueFormat = new ArcGISValueFormat(popup);
					ArcGISTitleView titleView = (ArcGISTitleView) popup
							.getLayout().getTitleView();
					titleView.setGeometryInfo(getGeometryInfo(gr, valueFormat));
				}
				mPopupFragment.addPopup(popup);
				mPopupFragment.show();
			}

		}

	}
}
/* 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.
 *
 */

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

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

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.ArcGISPopupInfo;
import com.esri.android.map.popup.Popup;
import com.esri.android.map.popup.PopupContainer;
import com.esri.android.map.popup.PopupContainerView;
import com.esri.core.map.popup.PopupInfo;

/*
 * A fragment to display pop-ups and handle user interactions with a pop-up. 
 */
public class PopupFragment extends Fragment {
	
	private PopupContainer mPopupContainer;
	private MapView mMapView;
	private boolean mIsInitialize, mIsDisplayed;
	private OnEditListener mEditListener;

	public PopupFragment() {
		mIsInitialize = false;
		mIsDisplayed = false;
	}
	
	public PopupFragment(MapView mapView) {
		this.mMapView = mapView;
		mPopupContainer = new PopupContainer(mMapView);
		mIsInitialize = true;
		mIsDisplayed = false;
	}
	
	public PopupFragment(MapView mapView, PopupContainer container) {
    this.mMapView = mapView;
    this.mPopupContainer = container;
    if (mPopupContainer != null)
      mIsInitialize = true;
    else
      mIsInitialize = false;
    mIsDisplayed = false;
  }
	
	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		
		// Set listener to handle editing events
		mEditListener = (OnEditListener) activity;
	}
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		// Create popupcontainer if it hasn't been created
		if (mPopupContainer == null) {
		  mPopupContainer = new PopupContainer(mMapView);
			mIsInitialize = true;
		}
		
		// Fragment wants to add menu to action bar
		setHasOptionsMenu(true);
	}
	
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
	  
		PopupContainerView view = null;
		
		if (mPopupContainer != null) {
			view = mPopupContainer.getPopupContainerView();
			view.setOnPageChangelistener(new OnPageChangeListener() {
				
				@Override
				public void onPageSelected(int arg0) {
					
				}
				
				@Override
				public void onPageScrolled(int arg0, float arg1, int arg2) {
					// Refresh menu item while swipping popups
					Activity activity = (Activity)mMapView.getContext();
					activity.invalidateOptionsMenu();
				}
				
				@Override
				public void onPageScrollStateChanged(int arg0) {
					
				}
			});
		}
		return view;
	}

	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
		// Inflate the menu; this adds items to the action bar if it is present.
		inflater.inflate(R.menu.popup_activity, menu);
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if ((mPopupContainer == null) || (mPopupContainer.getPopupCount() <= 0))
			return true;
		
		Popup popup = mPopupContainer.getCurrentPopup();
	    switch(item.getItemId()){
		    case R.id.menu_camera:
	      	startActivityForResult(new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI), 1);
	       break;
		    case R.id.menu_delete:
	      	deleteFeature(popup);
	       break;
	      case R.id.menu_edit:
	        ViewGroup view = popup.getLayout().getLayout();
	        LinearLayout ll = (LinearLayout)view.findViewById(R.id.second_inner_linearlayout);
	        if (ll != null)
	          ll.setVisibility(View.GONE);
	      	editFeature(popup);
	        break;
	      case R.id.menu_save:
	      	saveFeature(popup);
	       break;
	    }
	    
		return true;
	}
	
	@Override
	public void onPrepareOptionsMenu(Menu menu) {
	
		// Turn on/off menu items based on popup's edit capabilities
		for (int i = 0; i < menu.size(); i++) {
			MenuItem item = menu.getItem(i);
			if (mPopupContainer != null) {
				Popup popup = mPopupContainer.getCurrentPopup();
				if (popup != null) {
					if (popup.isEditMode() ) {
						if ((item.getItemId() == R.id.menu_save) || (item.getItemId() == R.id.menu_camera)) {
							item.setVisible(true);
							item.setEnabled(true);
						}
						else {
							item.setVisible(false);
							item.setEnabled(false);
						}
					}
					else {
						if (((item.getItemId() == R.id.menu_edit) && (popup.isEditable()))
								|| ((item.getItemId() == R.id.menu_delete) && (popup.isDeletable()))) {
							item.setVisible(true);
							item.setEnabled(true);
						} else {
							item.setVisible(false);
							item.setEnabled(false);
						}
					}
				} else {
					item.setVisible(false);
					item.setEnabled(false);
				}
			} else {
				item.setVisible(false);
				item.setEnabled(false);
			}
		}
	}
	
	public void addPopup(Popup popup) {
		if (mPopupContainer != null) 
			mPopupContainer.addPopup(popup);
	}

	// Indicate if popupcontainer has been created
	public boolean isInitialize() {
		return mIsInitialize;
	}

	public void setInitialize(boolean isInitialize) {
		this.mIsInitialize = isInitialize;
	}

	// Indicate if fragment is displayed
	public boolean isDisplayed() {
		return mIsDisplayed;
	}

	public void setDisplayed(boolean isDisplayed) {
		this.mIsDisplayed = isDisplayed;
	}
	
	//Display 
  public void show() {
    if (mIsDisplayed)
      return;
    
    FragmentActivity activity = (FragmentActivity) mMapView.getContext();
    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
    transaction.setCustomAnimations(R.anim.popup_rotate_in, R.anim.popup_rotate_out);
    transaction.add(android.R.id.content, this, null);
    transaction.addToBackStack(null);
    transaction.commit();
    setDisplayed(true);
  }
	
	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		 if ((resultCode == Activity.RESULT_OK) 
		     && (data != null) && (mPopupContainer != null)) {
	    	// Add the selected media as attachment.
	      Uri selectedImage = data.getData();
	      mPopupContainer.getCurrentPopup().addAttachment(selectedImage);
	    }
	}
	
	// When "delete" menu item is clicked
	private void deleteFeature(Popup popup) {
		ArcGISFeatureLayer featureLayer = getFeatureLayer(popup);
		mEditListener.onDelete(featureLayer, popup);
	}
	
	// When "edit" menu item is clicked
	private void editFeature(Popup popup) {
		ArcGISFeatureLayer featureLayer = getFeatureLayer(popup);
		mEditListener.onEdit(featureLayer, popup);
	}
	
	// When "save" menu item is clicked
	private void saveFeature(Popup popup) {
		ArcGISFeatureLayer featureLayer = getFeatureLayer(popup);
		mEditListener.onSave(featureLayer, popup);
	}
	
	// Get the feature layer which is associated with the current popup
	private ArcGISFeatureLayer getFeatureLayer(Popup popup) {
		ArcGISFeatureLayer featureLayer = null;
		
		if ((mMapView == null) || (popup == null))
			return null;
		PopupInfo popupInfo = popup.getPopupInfo();
		if (popupInfo instanceof ArcGISPopupInfo) {
			ArcGISPopupInfo agsPopupInfo = (ArcGISPopupInfo) popupInfo;
			Layer[] layers = mMapView.getLayers();
			for (Layer layer : layers) {
				if ((layer instanceof ArcGISFeatureLayer) 
						&& (layer.getUrl().compareToIgnoreCase(agsPopupInfo.getLayerUrl()) == 0)) {
					featureLayer = (ArcGISFeatureLayer) layer;
					return featureLayer;
				}
			}
		}
		
		return featureLayer;
	}
	
	// Listener to handle editing events
	public interface OnEditListener {
		public void onDelete(ArcGISFeatureLayer fl, Popup popup);
		public void onEdit(ArcGISFeatureLayer fl, Popup popup);
		public void onSave(ArcGISFeatureLayer fl, Popup popup);
	}

}
Feedback on this topic?