Android Open Source - EDCA Geo Helper






From Project

Back to project page EDCA.

License

The source code is released under:

GNU General Public License

If you think the Android project EDCA listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package se.lu.nateko.edca.svc;
/*from   www.  ja v a  2 s.c o m*/
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import se.lu.nateko.edca.BackboneSvc;
import se.lu.nateko.edca.LayerViewer;
import se.lu.nateko.edca.R;
import se.lu.nateko.edca.Utilities;
import se.lu.nateko.edca.svc.GeographyLayer.LayerField;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Environment;
import android.provider.Settings.Secure;
import android.util.Log;

import com.google.android.gms.maps.model.LatLng;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;

/********************************COPYRIGHT***********************************
 * This file is part of the Emergency Data Collector for Android (EDCA).  *
 * Copyright  2013 Mattias Spngmyr.                    *
 *                                       *
 *********************************LICENSE************************************
 * EDCA is free software: you can redistribute it and/or modify it under  *
 * the terms of the GNU General Public License as published by the Free    *
 * Software Foundation, either version 3 of the License, or (at your    *
 * option) any later version.                        *
 *                                      *
 * EDCA is distributed in the hope that it will be useful, but WITHOUT ANY  *
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or    *
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License  *
 * for more details.                            *
 *                                      *
 * You should have received a copy of the GNU General Public License along  *
 * with EDCA. If not, see "http://www.gnu.org/licenses/".          *
 *                                       *
 * The latest source for this software can be accessed at          *
 * "github.org/mattiassp/edca".                        *
 *                                       *
 * EDCA also utilizes the JTS Topology Suite, Version 1.8 by Vivid      *
 * Solutions Inc. It is released under the Lesser General Public License  *
 * ("http://www.gnu.org/licenses/") and its source can be accessed at the  *
 * JTS Topology Suite website ("http://www.vividsolutions.com/jts").    *
 *                                       *
 * Android is a trademark of Google Inc. The Android source is released  *
 * under the Apache License 2.0                        *
 * ("http://www.apache.org/licenses/LICENSE-2.0") and can be accessed at  *
 * "http://source.android.com/".                      *
 *                                       *
 * For other enquiries, e-mail to: edca.contact@gmail.com          *
 *                                       *
 ****************************************************************************
 * Helper class for writing a GeographyLayer's data to the external      *
 * storage, and reading and interpreting such stored files into one. Also  *
 * handles uploading of a layer's data to its corresponding server layer.  *
 *                                       *
 * @author Mattias Spngmyr                          *
 * @version 0.57, 2013-08-05                        *
 *                                       *
 ****************************************************************************/
public class GeoHelper extends AsyncTask<GeographyLayer, Void, GeoHelper> {
  /** The error tag for this ASyncTask. */
  public static final String TAG = "GeoHelper";
  /** Constant defining the wait time (seconds) before an upload operation times out. */
  private static final int TIME_OUT = 60;
  
  /** The mode to run, being either of "read", "write" or "overwrite". */
  private int mRwMode = 0;
  /** Constant identifying the run mode "read" (from external storage). */
  public static final int RWMODE_READ = 0;
  /** Constant identifying the run mode "write" (to external storage, without overwriting). */
  public static final int RWMODE_WRITE = 1;
  /** Constant identifying the run mode "overwrite" (to external storage). */
  public static final int RWMODE_OVERWRITE = 2;
  /** Constant identifying the run mode "upload" (to server). */
  public static final int RWMODE_UPLOAD = 3;
  
  /** System class used to keep track of external storage states, e.g. if it is connected or not, and events. */
  private BroadcastReceiver mExternalStorageReceiver;
  /** Whether or not the external storage is available. */
  private boolean mExternalStorageAvailable = false;
  /** Whether or not it is possible to write to the external storage. */
  private boolean mExternalStorageWriteable = false;
  
  /** A reference to the application's background Service, received in the constructor. */
  private BackboneSvc mService;
  /** The HttpClient to use for uploading the data. */
  private HttpClient mHttpClient;
  /** The active GeographyLayer from which to upload data. */
  private GeographyLayer mGeoLayer;
  /** The id of the layer to activate in the local SQLite database in case of running the "read" mode. */
  private Long mRowId;
  /** The new ID:s on the server of the features uploaded. */
  private ArrayList<Long> mInsertedIDs;
  /** Whether or not the helper has succeeded with the requested operation. */
  private boolean mSuccess = false;
  /** Whether or not a file conflict has occurred; a file with the same name as that being saved already exists. */
  private boolean mFileConflict = false;
  /** The XML namespace to use, the same as the name of the workspace on the geospatial server. */
  private String mNamespace;
  /** Constant defining what spatial reference system (srs) to use for sending geometry to the geospatial server. */
  private static final String SRS = "srsName=\"http://www.opengis.net/gml/srs/epsg.xml#4326\"";
    
  /**
   * Basic constructor setting up the upload/write mode and the BackboneSvc reference.
   * @param service The database manager that issued the request and through which the UI thread can be reached.
   * @param rwMode The mode to run, being either of "read", "write" or "overwrite".
   */
  public GeoHelper(BackboneSvc service, int rwMode) {
//    Log.d(TAG, "GeoHelper(BackboneSvc, int) called.");
    mService = service;
    mRwMode = rwMode;
  }
  
  /**
   * Read mode constructor setting up the read mode, the id of the layer to activate and the BackboneSvc reference.
   * @param service The database manager that issued the request and through which the UI thread can be reached.
   * @param rwMode The mode to run, being either of "read", "write" or "overwrite".
   * @param rowId The id of the layer to activate in the local SQL database.
   */
  public GeoHelper(BackboneSvc service, int rwMode, long rowId) {
//    Log.d(TAG, "GeoHelper(BackboneSvc, int, long) called.");
    mService = service;
    mRwMode = rwMode;
    mRowId = rowId;
  }

  protected GeoHelper doInBackground(GeographyLayer... layers) {
    Log.d(TAG, "doInBackground(GeographyLayer...) called.");
    mGeoLayer = layers[0]; // Store the GeographyLayer reference.
    
    if(mRwMode != RWMODE_UPLOAD)
      startWatchingExternalStorage(); // Start to listen for changes in the storage state, upon which the operations may have to cancel.    
    
    /* Perform the task unless the storage is not available. */
    switch(mRwMode) {
        case RWMODE_READ: {
          if(!mExternalStorageAvailable)
            mService.showAlertDialog(mService.getString(R.string.service_sdunavailable), null);
          else
            mSuccess = readIntoGeographyLayer();
          break;
        }
        case RWMODE_WRITE: {
          if(!mExternalStorageWriteable)
            mService.showAlertDialog(mService.getString(R.string.service_sdunavailable), null);
          else
            mSuccess = writeFromGeographyLayer(false);
          break;          
        }
        case RWMODE_OVERWRITE: {
          if(!mExternalStorageWriteable)
            mService.showAlertDialog(mService.getString(R.string.service_sdunavailable), null);
          else
            mSuccess = writeFromGeographyLayer(true);
          break;
        }
        case RWMODE_UPLOAD: {
          try  { // Get or wait for exclusive access to the HttpClient.
            mHttpClient = mService.getHttpClient();
            mService.startAnimation(); // Start the animation, showing that a web communicating thread is active.
          } catch(InterruptedException e) { Log.e(TAG, "Thread " + Thread.currentThread().getId() + ": " + e.toString()); }
          
          mSuccess = upload();
          mService.unlockHttpClient(); // Release the lock on the HttpClient, allowing new connections to be made.
          break;
        }
        default:
          Log.e(TAG, "Invalid read/write mode.");
      }
    
    return this;
  }

  @Override
  protected void onProgressUpdate(Void... values) {
    Log.d(TAG, "onProgressUpdate(Void...) called.");
    // No progress update.
    super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(GeoHelper result) {
    Log.d(TAG, "onPostExecute(GeoHelper) called.");
    if(mRwMode != RWMODE_UPLOAD)
      stopWatchingExternalStorage(); // No more need to watch for storage state changes.
    
    if(mSuccess) {
      if(mRwMode == RWMODE_READ) {
        mService.deactivateLayers();
        Cursor modeCursor = mService.getSQLhelper().fetchData(LocalSQLDBhelper.TABLE_LAYER, LocalSQLDBhelper.KEY_LAYER_COLUMNS, mRowId, null, false);
        mService.getActiveActivity().startManagingCursor(modeCursor);
        if(modeCursor.moveToFirst()) {
          mService.getSQLhelper().updateData(LocalSQLDBhelper.TABLE_LAYER, modeCursor.getLong(0), LocalSQLDBhelper.KEY_LAYER_ID, new String[]{LocalSQLDBhelper.KEY_LAYER_USEMODE}, new String[]{String.valueOf(modeCursor.getLong(2) * LocalSQLDBhelper.LAYER_MODE_ACTIVE)}); // Add the "Active" mode to the current mode.
          mService.updateLayoutOnState();
        }
        mService.showToast(R.string.layerviewer_uploadtoast_readsuccess);
      }
      else if(mRwMode == RWMODE_UPLOAD) {
        mService.stopAnimation(); // Stop the animation, showing that a web communicating thread is no longer active.
        mService.showToast(R.string.layerviewer_uploadtoast_uploadsuccess);
      }
      else { // Successful write operation.
        Log.i(TAG, "Write operation successful.");
        Cursor modeCursor = mService.getSQLhelper().fetchData(LocalSQLDBhelper.TABLE_LAYER, LocalSQLDBhelper.KEY_LAYER_COLUMNS, LocalSQLDBhelper.ALL_RECORDS, LocalSQLDBhelper.KEY_LAYER_NAME + " = " + "\"" + mGeoLayer.getName() + "\"", false);
        mService.getActiveActivity().startManagingCursor(modeCursor);
        if(modeCursor.moveToFirst()) {
          if(modeCursor.getLong(2) % LocalSQLDBhelper.LAYER_MODE_STORE != 0) // Don't add the stored mode more than once.
            mService.getSQLhelper().updateData(LocalSQLDBhelper.TABLE_LAYER, modeCursor.getLong(0), LocalSQLDBhelper.KEY_LAYER_ID, new String[]{LocalSQLDBhelper.KEY_LAYER_USEMODE}, new String[]{String.valueOf(modeCursor.getLong(2) * LocalSQLDBhelper.LAYER_MODE_STORE)}); // Add the "Stored" mode to the current mode.
          mService.updateLayoutOnState();
          mService.showToast(R.string.layerviewer_uploadtoast_writesuccess);
        } else {Log.e(TAG, "No such layer.");}
      }
    }
    else {
      if(mRwMode == RWMODE_READ)
        mService.showToast(R.string.layerviewer_uploadtoast_readfail);
      else if(mRwMode == RWMODE_WRITE && mFileConflict) {
        /* If write failed because of a file conflict, ask to overwrite the data. */
        new AlertDialog.Builder(mService.getActiveActivity())
              .setIcon(android.R.drawable.ic_dialog_alert)
              .setTitle(R.string.layerviewer_overwritealert_title)
              .setMessage(R.string.layerviewer_overwritealert_msg)
              .setPositiveButton(R.string.layerviewer_overwritealert_confirm, new DialogInterface.OnClickListener()
              {
                public void onClick(DialogInterface dialog, int which) {
                  mService.makeSaveOperation(GeoHelper.RWMODE_OVERWRITE);
                  dialog.dismiss();    
                }
              })
              .setNegativeButton(R.string.layerviewer_overwritealert_decline, null)
              .show();
      }
      else if(mRwMode == RWMODE_UPLOAD) {
        /* If an upload failed, ask to store the data locally. */
        mService.stopAnimation(); // Stop the animation, showing that a web communicating thread is no longer active.
        mService.clearConnection(false); // Clear the failed connection without reporting (specialized report below).
        
        new AlertDialog.Builder(mService.getActiveActivity())
            .setIcon(android.R.drawable.ic_menu_upload)
            .setTitle(R.string.layerviewer_uploaddialog_title)
            .setMessage(R.string.layerviewer_uploaddialog_msg)
            .setPositiveButton(R.string.layerviewer_uploaddialog_confirm, new DialogInterface.OnClickListener()
            {
              public void onClick(DialogInterface dialog, int which) {
                mService.makeSaveOperation(GeoHelper.RWMODE_WRITE);
                dialog.dismiss();    
              }
            })
            .setNegativeButton(R.string.layerviewer_uploaddialog_decline, null)
            .show();
      }
      else
        mService.showToast(R.string.layerviewer_uploadtoast_writefail);
    }
    
    /* Reactivate the upload button and set the flag in the BackboneSvc. */
    mService.setUploading(false);
    if(mService.getActiveActivity().getLocalClassName().equalsIgnoreCase("LayerViewer"))
      ((LayerViewer) mService.getActiveActivity()).setLayout_UploadButton(true, false);
    
    super.onPostExecute(result);
  }
  
  /**
   * Reads the geometry and attribute data from the local storage
   * into the active GeographyLayer.
   */
  private boolean readIntoGeographyLayer() {
    Log.d(TAG, "readIntoGeographyLayer() called.");
    File layerPath = new File(getPath());
    
    File geomFile = new File(layerPath, "geometry.txt");
    File attFile = new File(layerPath, "attributes.txt");
    try {
      if(!geomFile.exists()) // If there is no geometry file.
        Log.i(TAG, "No geometry stored.");
      else {
        /* Read the geometry with ID and geometry WKT. */
          Scanner scanner = new Scanner(new FileInputStream(geomFile));
          
          try {
            while(scanner.hasNextLine()) {
              String[] line = scanner.nextLine().split("[ ]{1}", 2);
              WKTReader reader = new WKTReader();
              switch(mGeoLayer.getTypeMode()) {
                  case GeographyLayer.TYPE_POINT: {
                    Coordinate p = ((Point) reader.read(line[1])).getCoordinate();
                    mGeoLayer.addGeometry(new LatLng(p.y, p.x), Long.parseLong(line[0]));
                    break;
                  }
                  case GeographyLayer.TYPE_LINE: {
                    Geometry geom = reader.read(line[1]);
                    Log.v(TAG, "Geom type: " + geom.getGeometryType());
                    if(geom.getGeometryType().equalsIgnoreCase("LineString"))
                      mGeoLayer.addLine((LineString) geom, Long.parseLong(line[0]), true);
                    else if(geom.getGeometryType().equalsIgnoreCase("Point")) {
                      Coordinate p = ((Point) geom).getCoordinate();
                      mGeoLayer.addGeometry(new LatLng(p.y, p.x));
                    }
                    break;
                  }
                  case GeographyLayer.TYPE_POLYGON: {
                    Geometry geom = reader.read(line[1]);
                    Log.v(TAG, "Geom type: " + geom.getGeometryType());
                    if(geom.getGeometryType().equalsIgnoreCase("Polygon"))
                      mGeoLayer.addPolygon((Polygon) reader.read(line[1]), Long.parseLong(line[0]), true);
                    else if(geom.getGeometryType().equalsIgnoreCase("Point")) {
                      Coordinate p = ((Point) geom).getCoordinate();
                      mGeoLayer.addGeometry(new LatLng(p.y, p.x));
                    }
                    break;
                  }
              }
            }
          } catch (ParseException e) {
            Log.e(TAG, e.toString());
            return false;
          } finally {
            scanner.close();
          }
      }
      if(!attFile.exists()) // If there is no attribute file.
        Log.i(TAG, "No attributes stored.");
      else {
        /* Read the attributes with ID and field info. */
        Scanner scanner = new Scanner(new FileInputStream(attFile));
        try {
          while(scanner.hasNextLine()) {
                String[] line = scanner.nextLine().split("[;]{1}",-1);
                for(int i=0; i < mGeoLayer.getNonGeomFields().size(); i++)
                  mGeoLayer.addAttribute(Long.parseLong(line[0]), mGeoLayer.getNonGeomFields().get(i).getName(), line[i+1]);
            }
        } finally {
          scanner.close();
        }
      }
    } catch (IOException e) {
        Log.e(TAG, e.toString());
        return false;
    }
    return true;
  }
  
  /**
   * Write the geography of the layer to files on the external
   * storage.
   * @param overwrite Determines whether or not to overwrite existing data. If false and there are already stored data on this layer, the method will have no effect.
   * @return True if successful.
   */
  private boolean writeFromGeographyLayer(boolean overwrite) {
    Log.d(TAG, "writeFromGeographyLayer(" + String.valueOf(overwrite) + ") called.");
    
    File layerPath = new File(getPath());
    layerPath.mkdirs();
    File geomFile = new File(layerPath, "geometry.txt");
    File attFile = new File(layerPath, "attributes.txt");
    
    if(overwrite || (!geomFile.exists() && !attFile.exists())) { // If there are no files with the given names, or the mode is set to overwrite.
      try {
        /* Store the points with ID and geometry WKT. */
          FileWriter outGeom = new FileWriter(geomFile);
          FileWriter outAtt = new FileWriter(attFile);
          if(mGeoLayer.getTypeMode() == GeographyLayer.TYPE_POINT)
            outGeom.write(formGeomString());
          else {
            try {
              String sequenceGeom = formSequenceGeomString();
              if(!sequenceGeom.equalsIgnoreCase(""))
                sequenceGeom = sequenceGeom + System.getProperty("line.separator");
              outGeom.write(sequenceGeom + formUnusedPointString());
            } catch (Exception e) {
              Log.e(TAG, e.toString());
              outGeom.close();
              outAtt.close();
              return false;
            }
          }
          outGeom.close();
          outAtt.write(formAttString());
          outAtt.close();
          return true;
      }
      catch (IOException e) {
          Log.e(TAG, e.toString());
          return false;
      }
    }
    else {
      Log.w(TAG, "File already exists. Use overwrite mode?");
      mFileConflict = true;
      return false;      
    }
  }
  
  /**
   * Removes the Layer's data from the external storage.
   * @param layerName The name of the layer to delete.
   */
  public static void deleteGeographyLayer(String layerName) {
//    Log.d(TAG, "deleteGeographyLayer(layerName=" + layerName + ") called.");
    File layerPath = new File(Environment.getExternalStorageDirectory().toString() + "/Android/data/" + BackboneSvc.PACKAGE_NAME + "/files/" + Utilities.dropColons(layerName, Utilities.RETURN_LAST));
    Utilities.deleteRecursive(layerPath);
  }
  
  /**
   * Checks if the layer with the specified name is stored
   * on the external storage.
   * @return True of the layer is stored on the device.
   */
  public static boolean isStored(String layerName) {
    Log.d(TAG, "isStored(String) called.");
    String path = new String(Environment.getExternalStorageDirectory().toString() + "/Android/data/" + BackboneSvc.PACKAGE_NAME + "/files/" + Utilities.dropColons(layerName, Utilities.RETURN_LAST));
    return (new File(path)).exists();
  }
  
  /**
   * Uploads the active layer's data to the geospatial server
   * and deletes the data from the device.
   * @return True if successful.
   */
  private boolean upload() {
    Log.d(TAG, "upload() called.");
    /* Try to form an URI from the supplied ServerConnection info. */
    if(mService.getActiveServer() == null) // Cannot connect unless there is an active connection.
      return false;
    String uriString = mService.getActiveServer().getAddress() + "/wfs";
    Log.i(TAG, uriString);
    
    /* Post all geometry from the active layer to that layer on the geospatial server. */
    HttpResponse response;
    StringEntity se;
    boolean responseSuccessful = false;
    StringBuilder stringTotal = new StringBuilder();
    try {
      final HttpParams httpParameters = mHttpClient.getParams();
      HttpConnectionParams.setConnectionTimeout(httpParameters, TIME_OUT * 1000);
      HttpConnectionParams.setSoTimeout        (httpParameters, TIME_OUT * 1000);

      se = new StringEntity(formTransactXML());
      se.setContentType("text/xml");
      HttpPost postRequest = new HttpPost(uriString);
      postRequest.setEntity(se);
      
      response = mHttpClient.execute(postRequest);
      InputStream xmlStream = response.getEntity().getContent();
      
      BufferedReader r = new BufferedReader(new InputStreamReader(xmlStream));
      String line;
      while ((line = r.readLine()) != null)
          stringTotal.append(line);
      
      InputStream is = new ByteArrayInputStream(stringTotal.toString().getBytes("UTF-8"));
      responseSuccessful = parseXMLResponse(is);
      
    } catch(UnsupportedEncodingException e) {
      Log.e(TAG, e.toString());
      return false;
    } catch (ClientProtocolException e) {
      Log.e(TAG, e.toString());
      return false;
    } catch (IOException e) {
      Log.e(TAG, e.toString());
      return false;
    }
    Log.i(TAG, "Insert Response: " + stringTotal.toString());
    
    try { // Consume the HttpEntity.
      se.consumeContent();
    } catch (UnsupportedOperationException e) {
      Log.e(TAG, "Operation unsupported by streaming entity sub-class: " + e.toString());
    } catch (IOException e) {
      Log.e(TAG, "Entity consumed?: " + e.toString());
      e.printStackTrace();
    }
    
    if(responseSuccessful) {
      /* Remove all uploaded geometry from the active layer and the local storage. */
      mGeoLayer.clearGeometry(false);
      
      deleteGeographyLayer(mGeoLayer.getName());    
    
      /* Update the layer table to reflect that the layer is no longer stored on the device. */
      Cursor layerCursor = mService.getSQLhelper().fetchData(LocalSQLDBhelper.TABLE_LAYER, LocalSQLDBhelper.KEY_LAYER_COLUMNS, LocalSQLDBhelper.ALL_RECORDS, new String(LocalSQLDBhelper.KEY_LAYER_NAME + " = \"" + Utilities.dropColons(mGeoLayer.getName(), Utilities.RETURN_LAST) +"\""), false);
      mService.getActiveActivity().startManagingCursor(layerCursor);
      if(layerCursor.moveToFirst()) {
        long layerid = layerCursor.getInt(0);
        int layerMode = layerCursor.getInt(2);
        if(layerMode % LocalSQLDBhelper.LAYER_MODE_STORE == 0) // If the layer is currently stored, remove that mode.
          mService.getSQLhelper().updateData(LocalSQLDBhelper.TABLE_LAYER, layerid, LocalSQLDBhelper.KEY_LAYER_ID, new String[]{LocalSQLDBhelper.KEY_LAYER_USEMODE}, new String[]{String.valueOf(layerMode / LocalSQLDBhelper.LAYER_MODE_STORE)});
      }
      return true;
    } else return false;
    
  }
  
  /**
   * Start to listen for changes in the storage state,
   * and report any changes to handleStorageChange.
   */
  private void startWatchingExternalStorage() {
    Log.d(TAG, "startWatchingExternalStorage() called.");
      mExternalStorageReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              Log.i("TAG", "Storage state changed: " + intent.getData());
              updateExternalStorageState();
              handleStorageChange();
          }
      };
      IntentFilter filter = new IntentFilter();
      filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
      filter.addAction(Intent.ACTION_MEDIA_REMOVED);
      mService.registerReceiver(mExternalStorageReceiver, filter);
      updateExternalStorageState();
  }

  /**
   * Stop listening for external storage state changes.
   */
  private void stopWatchingExternalStorage() {
    Log.d(TAG, "stopWatchingExternalStorage() called.");
      mService.unregisterReceiver(mExternalStorageReceiver);
  }
  
  /**
   * Checks the state of the external storage to determine
   * what interactions can be performed.
   */
  private void updateExternalStorageState() {
//    Log.d(TAG, "updateExternalStorageState() called.");
    String state = Environment.getExternalStorageState();

    /* Check the availability of the external storage. */
    if (Environment.MEDIA_MOUNTED.equals(state)) { // We can read and write the media.        
        mExternalStorageAvailable = mExternalStorageWriteable = true;
        
    } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // We can only read the media.
        mExternalStorageAvailable = true;
        mExternalStorageWriteable = false;
        
    } else // We can neither read nor write.        
        mExternalStorageAvailable = mExternalStorageWriteable = false;
  }
  
  /**
   * Pauses or resumes this AsyncTask if the external storage
   * becomes unavailable or is available again.
   */
  synchronized private void handleStorageChange() {
//    Log.d(TAG, "handleStorageChange() called.");
    /* The external storage has become unavailable, pause the thread. */
    if(!mExternalStorageAvailable) {
      Log.w(TAG, "External storage made unavailable. Thread put on hold.");
      try {
        this.wait();
      } catch (InterruptedException e) {
        Log.e(TAG, e.toString());
        e.printStackTrace();
      }
    }
    /* The external storage cannot be written to and the mode requires this, pause the thread. */
    else if(!mExternalStorageWriteable && (mRwMode == RWMODE_WRITE || mRwMode == RWMODE_OVERWRITE)) {
      Log.w(TAG, "External storage made unavailable. Thread put on hold.");
      try {
        this.wait();
      } catch (InterruptedException e) {
        Log.e(TAG, e.toString());
        e.printStackTrace();
      }
    }
    /* The external storage can now be written to, resume the thread. */
    else if(mExternalStorageWriteable) {
      this.notify();
      Log.w(TAG, "External storage made available. Thread resumed.");
    }
    /* The external storage has become available and the mode only requires reading, resume the thread. */
    else if(mExternalStorageAvailable && mRwMode == RWMODE_READ) {
      this.notify();
      Log.w(TAG, "External storage made available. Thread resumed.");
    }    
  }
  
  /**
   * Get method for path to the geometry on the external storage.
   * The path does not include layer namespace.
   * @return The layer path.
   */
  public String getPath() {
//    Log.d(TAG, "getPath() called.");
    return new String(Environment.getExternalStorageDirectory().toString() + "/Android/data/" + BackboneSvc.PACKAGE_NAME + "/files/" + Utilities.dropColons(mGeoLayer.getName(), Utilities.RETURN_LAST));
  }
  
  /**
   * Forms a single string containing all the point geometry objects in the GeographyLayer.
   * @return A string with the point geometry id and WKT on each row.
   */
  private String formGeomString() {
    Log.d(TAG, "formGeomString() called.");
    if(!mGeoLayer.hasGeometry()) // Return empty string if there is no geometry to form a string from.
      return new String("");
    
    Set<Long> keys;
    String result = "";
    
    keys = mGeoLayer.getGeometry().keySet();
      
    Collection<LatLng> values = mGeoLayer.getGeometry().values();
    Iterator<LatLng> i = values.iterator();
          
    for(Long id : keys) {
      LatLng loc = i.next();
      Point p = mService.getGeometryFactory().createPoint(new Coordinate(loc.longitude, loc.latitude));
      result = result + String.valueOf(id) + " " + String.valueOf(p.toText());
      if(i.hasNext())
        result = result + System.getProperty("line.separator");
    }
    return result;
  }
  
  /**
   * Forms a single string containing all the line or polygon geometry objects in the GeographyLayer.
   * @return A string with the line or polygon id and WKT on each row.
   */
  private String formSequenceGeomString() throws Exception {
    Log.d(TAG, "formSequenceGeomString() called.");
    if(!mGeoLayer.hasGeometry()) // Return empty string if there is no geometry to form a string from.
      return new String("");
    
    Set<Long> keys;
    String result = "";
    
    keys = mGeoLayer.getPointSequence().keySet();
    Iterator<Long> it = keys.iterator();
      
    while(it.hasNext()) {
      Long id = it.next();
      ArrayList<Long> sequence = mGeoLayer.getPointSequence().get(id);
        
      Coordinate[] coordinates = new Coordinate[sequence.size()];
      for(int i=0; i < sequence.size(); i++) {
        LatLng loc = mGeoLayer.getGeometry().get(sequence.get(i));
        coordinates[i] = new Coordinate(loc.longitude, loc.latitude);
      }

      Geometry sequenceGeom;
      try {
        if(mGeoLayer.getTypeMode() == GeographyLayer.TYPE_POLYGON)
          sequenceGeom = mService.getGeometryFactory().createPolygon(mService.getGeometryFactory().createLinearRing(coordinates), null);
        else
          sequenceGeom = mService.getGeometryFactory().createLineString(coordinates);
        
        result = result + String.valueOf(id) + " " + String.valueOf(sequenceGeom.toText());
      } catch (Exception e) {
        Log.e(TAG, e.toString());
        mService.showToast(R.string.mapviewer_combinesequence_invalid); // Notice the user of invalid point sequences.
        Exception returnExc = new Exception("Could not create geometry.");
        throw returnExc;
      }        
      if(it.hasNext())
        result = result + System.getProperty("line.separator");
    }
    return result;
  }
  
  /**
   * Forms a single string containing all the point geometry objects in the GeographyLayer
   * that are not included in a point sequence.
   * @return A string with unused points represented by their id and WKT on each row.
   */
  private String formUnusedPointString() { 
    Log.d(TAG, "formUnusedPointString() called.");
    if(!mGeoLayer.hasGeometry()) // Return empty string if there is no geometry to form a string from.
      return new String("");
    
    String result = "";
    
    /* Go through all points and add points that are not included in a point sequence. */
    Set<Long> keys = mGeoLayer.getGeometry().keySet();
    Iterator<Long> it = keys.iterator();
    boolean first = true;
    while(it.hasNext()) {
      Long id = it.next();
      if(mGeoLayer.pointInSequence(id) == -1) {// If the point is not in any point sequence:
        LatLng loc = mGeoLayer.getGeometry().get(id);
        Point p = mService.getGeometryFactory().createPoint(new Coordinate(loc.longitude, loc.latitude));
        if(first) { // Don't include a line separator before the first line.
          result = String.valueOf(id) + " " + p.toText();
          first = false;
        }
        else
          result = result + System.getProperty("line.separator") + String.valueOf(id) + " " + p.toText();
      }
    }
    return result;
  }
  
  /**
   * Forms a single string containing all the attributes of the objects in
   * the GeographyLayer.
   * @return A string with the geometry id and attributes on each row, separated by semi-colons.
   */
  private String formAttString() {
    Log.d(TAG, "formAttString() called.");
    if(mGeoLayer.getAttributes().size() < 1) // Return empty string if there are no attributes to form a string from.
      return new String("");
    
    Set<Long> keys = mGeoLayer.getAttributes().keySet();
    Collection<HashMap<String,String>> values = mGeoLayer.getAttributes().values();
    Iterator<HashMap<String,String>> i = values.iterator();
    
    String result = "";
    for(Long id : keys) {
      result = result + String.valueOf(id);      
      HashMap<String, String> map = i.next();
      for(int j = 0; j < mGeoLayer.getNonGeomFields().size(); j++) {
        result = result + ";" + map.get(mGeoLayer.getNonGeomFields().get(j).getName());
      }
      if(i.hasNext())
        result = result + System.getProperty("line.separator");
    }
    return result;
  }
  
  /**
   * Forms a single string containing an XML of a WFS Transaction Insert request.
   * @return A string with the WFS-T Insert XML. 
   */
  private String formTransactXML() {
    Log.d(TAG, "formTransactXML() called.");
    String request = "";
    if(!mGeoLayer.hasGeometry()) // Return empty string if there is no geometry to form a string from.
      return request;
    
    /* Set the namespace for the insert statements. */
    mNamespace = mService.getActiveServer().getWorkspace();
    if(mNamespace == null)
      mNamespace = Utilities.dropColons(mGeoLayer.getName(), Utilities.RETURN_FIRST);
    else if(mNamespace.equalsIgnoreCase(""))
      mNamespace = Utilities.dropColons(mGeoLayer.getName(), Utilities.RETURN_FIRST);
    
    /* Create a handle for the insert operation unique to this device. */
    String android_id = Secure.getString(mService.getActiveActivity().getContentResolver(), Secure.ANDROID_ID);
    String insertHandle = mService.getString(R.string.app_name_short);
    if(android_id != null)
      insertHandle = insertHandle + "." + android_id;
    
    request = request + "<wfs:Transaction service=\"WFS\" version=\"1.0.0\" handle=\"" + insertHandle + "\"" +
        " xmlns:" + mNamespace + "=\"" + mNamespace + "\"" +
        " xmlns:wfs=\"http://www.opengis.net/wfs\"" +
        " xmlns:gml=\"http://www.opengis.net/gml\"" +
        " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
        " xsi:schemaLocation=\"" +
        mService.getActiveServer().getAddress() +
        "/DescribeFeatureType?typename=" + mGeoLayer.getName() +
        " http://www.opengis.net/wfs\">";
    
    request = request + formInsertElements();
        
    request = request + " </wfs:Transaction>";
    
    Log.i(TAG, request);
    return request;
  }
  
  /**
   * Forms a single string containing the Insert XML elements of a WFS Transaction Insert request.
   * @return A string with the Insert elements of a WFS-T Insert XML. 
   */
  private String formInsertElements() {
//    Log.d(TAG, "formInsertElements() called.");

    /* The parts of the Insert statements to form. */
    // TODO Make static parts static final instance variables so they don't need to be instantiated for each GeoHelper.
    String inserts = ""; // The total, combined, result.
    String wfsStart = "";
    String geomFieldStart = "";
    String geomStart = "";
    String coords = "";
    String geomEnd = "";
    String geomFieldEnd = "";
    String atts = "";
    String wfsEnd = "";

    Set<Long> keyset = (mGeoLayer.getTypeMode() == GeographyLayer.TYPE_POINT) ? mGeoLayer.getGeometry().keySet() : mGeoLayer.getPointSequence().keySet(); // Fetch the set of keys pertaining to the relevant geometry type.
    
    for(Long featureId : keyset) {
      /* Form the WFS Insert element starters and enders. */
      wfsStart = " <wfs:Insert>" +
        " <" + mNamespace + ":" + Utilities.dropColons(mGeoLayer.getName(), Utilities.RETURN_LAST) + ">";
      wfsEnd = " </" + mNamespace + ":" + Utilities.dropColons(mGeoLayer.getName(), Utilities.RETURN_LAST) + ">" +
        " </wfs:Insert>";
      
      /* Form the Geometry field element starter and ender. */
      geomFieldStart = " <" + mNamespace + ":" + mGeoLayer.getGeomColumnKey() + ">";
      geomFieldEnd = " </" + mNamespace + ":" + mGeoLayer.getGeomColumnKey() + ">";
      
      /* Form the element starters and enders required by the corresponding geometry type before the coordinates. */
      if(mGeoLayer.getType().equalsIgnoreCase("gml:PointPropertyType")) {
        geomStart = " <gml:Point " + SRS + ">";
        geomEnd = " </gml:Point>";
      }
      else if(mGeoLayer.getType().equalsIgnoreCase("gml:MultiPointPropertyType")) {
        geomStart = " <gml:MultiPoint " + SRS + ">" + " <gml:pointMember>" + " <gml:Point>";
        geomEnd = " </gml:Point>" + " </gml:pointMember>" + " </gml:MultiPoint>";
      }
      else if(mGeoLayer.getType().equalsIgnoreCase("gml:LineStringPropertyType")) {
        geomStart = " <gml:LineString " + SRS + ">";
        geomEnd = " </gml:LineString>";
      }
      else if(mGeoLayer.getType().equalsIgnoreCase("gml:MultiLineStringPropertyType")) {
        geomStart = " <gml:MultiLineString " + SRS + ">" + " <gml:lineStringMember>" + " <gml:LineString>";
        geomEnd = " </gml:LineString>" + " </gml:lineStringMember>" + " </gml:MultiLineString>";
      }
      else if(mGeoLayer.getType().equalsIgnoreCase("gml:PolygonPropertyType") || mGeoLayer.getType().equalsIgnoreCase("gml:SurfacePropertyType")) {
        geomStart = " <gml:Polygon " + SRS + ">" + " <gml:outerBoundaryIs>" + " <gml:LinearRing>";
        geomEnd = " </gml:LinearRing>" + " </gml:outerBoundaryIs>" + " </gml:Polygon>";
      }
      else if(mGeoLayer.getType().equalsIgnoreCase("gml:MultiPolygonPropertyType") || mGeoLayer.getType().equalsIgnoreCase("gml:MultiSurfacePropertyType")) {
        geomStart = " <gml:MultiPolygon " + SRS + ">" + " <gml:polygonMember>" + " <gml:Polygon>" + " <gml:outerBoundaryIs>" + " <gml:LinearRing>";
        geomEnd = " </gml:LinearRing>" + " </gml:outerBoundaryIs>" + " </gml:Polygon>" + " </gml:polygonMember>" + " </gml:MultiPolygon>";
      }
      
      /* Form the coordinates elements. */
      if(mGeoLayer.getTypeMode() == GeographyLayer.TYPE_POINT) { // Form Point Coordinates GML.
        
        coords = " <gml:coordinates decimal=\".\" cs=\",\" ts=\" \">" +
            String.valueOf(mGeoLayer.getGeometry().get(featureId).longitude) +
            "," +
            String.valueOf(mGeoLayer.getGeometry().get(featureId).latitude) +
            "</gml:coordinates>";
      }
      else { // Form the sequence Coordinates GML.
        coords = " <gml:coordinates decimal=\".\" cs=\",\" ts=\" \">";        
        for(int i=0; i<mGeoLayer.getPointSequence().get(featureId).size(); i++)  { // For each point in this point sequence:
          if(i != 0)
            coords = coords + " "; // Insert spaces between coordinates.
          coords = coords +
              String.valueOf(mGeoLayer.getGeometry().get(mGeoLayer.getPointSequence().get(featureId).get(i)).longitude) +
              "," +
              String.valueOf(mGeoLayer.getGeometry().get(mGeoLayer.getPointSequence().get(featureId).get(i)).latitude);
        }
        coords = coords + "</gml:coordinates>";
      }
      
      /* Form the non-geometry fields and attributes, but only those with input. */
      try {
        atts = ""; // Reset the attributes string.
        for(LayerField field : mGeoLayer.getNonGeomFields()) {
          if(!mGeoLayer.getAttributes().get(featureId).get(field.getName()).equalsIgnoreCase("")) {
            atts = atts + " <" + mNamespace + ":" + field.getName() + ">"
                + mGeoLayer.getAttributes().get(featureId).get(field.getName()) +
                "</" + mNamespace + ":" + field.getName() + ">";
          }
        }
      } catch (NullPointerException e) { Log.e(TAG, "Didn't add attributes: " + e.toString()); }
      
      /* Combine all parts into a complete WFS Insert statement. */
      inserts = inserts + wfsStart + geomFieldStart + geomStart + coords + geomEnd + geomFieldEnd + atts + wfsEnd;
    }
    return inserts;
  }
  
  /**
   * Parses an XML response from a WFS Transaction (Insert) request and
   * reports if the response contains a Service Exception.
   * @param xmlResponse String containing the XML response from a DescribeFeatureType request.
   */
  protected boolean parseXMLResponse(InputStream xmlResponse) {
    Log.d(TAG, "parseXMLResponse(InputStream) called.");
    
    mInsertedIDs = new ArrayList<Long>();
    
    try {
      SAXParserFactory spfactory = SAXParserFactory.newInstance(); // Make a SAXParser factory.
      spfactory.setValidating(false); // Tell the factory not to make validating parsers.
      SAXParser saxParser = spfactory.newSAXParser(); // Use the factory to make a SAXParser.
      XMLReader xmlReader = saxParser.getXMLReader(); // Get an XML reader from the parser, which will send event calls to its specified event handler.
      XMLEventHandler xmlEventHandler = new XMLEventHandler();
      xmlReader.setContentHandler(xmlEventHandler); // Set which event handler to use.
      xmlReader.setErrorHandler(xmlEventHandler); // Also set where to send error calls.
      InputSource source = new InputSource(xmlResponse); // Make an InputSource from the XML input to give to the reader.
        xmlReader.parse(source); // Start parsing the XML.
    } catch (Exception e) {
      Log.e(TAG, e.toString());
      return false;
    }
    return true;
  }
  
  /**
   * Event handler class that handles calls from an XML reader.
   * @author Mattias Spngmyr
   * @version 0.01, 2012-09-21
   */
  private class XMLEventHandler extends DefaultHandler {
    /** The error tag for this XMLEventHandler. */
    public static final String TAG = "GeoHelper.XMLEventHandler";
    
    /** Constant defining the name of a "service exception" element. */
    private static final String ELEMENT_KEY_EXCEPTION = "ServiceException";
    /** Constant defining the name of an "exception" element. */
    private static final String ELEMENT_KEY_EXCEPTION_B = "Exception";
    /** Constant defining the name of a "feature id" element. */
    private static final String ELEMENT_KEY_ID = "FeatureId";
    /** Constant defining the name of a "failed" element. */
    private static final String ELEMENT_KEY_FAILED = "FAILED";
    /** Constant defining the name of a "message" element. */
    private static final String ELEMENT_KEY_MESSAGE = "Message";
    
    /** Constant identifying other elements than those with specific identifiers. */
    private static final int ELEMENT_MAPPING_DEFAULT = 0;
    /** Constant identifying a "service exception" element. */
    private static final int ELEMENT_MAPPING_EXCEPTION = 1;
    /** Constant identifying an "exception" element. */
    private static final int ELEMENT_MAPPING_EXCEPTION_B = 2;
    /** Constant identifying a "feature id" element. */
    private static final int ELEMENT_MAPPING_ID = 3;
    /** Constant identifying a "failed" element. */
    private static final int ELEMENT_MAPPING_FAILED = 4;
    /** Constant identifying a "message" element. */
    private static final int ELEMENT_MAPPING_MESSAGE = 5;
    
    /** Flag showing the type of element currently being parsed. */
    private int mElementType = 0;
    /** Flag indicating if the WFS-T Insert operation failed, i.e. if a forbidden (exception) element has been parsed. */
    public boolean mFailed = false;
    
    @Override
    public void startDocument() throws SAXException {
      Log.d(TAG, "startDocument() called: Examining Insert request response...");
      super.startDocument();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//      Log.d(TAG, "startElement(String, String, String, Attributes) called.");
      /* Record relevant element names as integer mappings. */
      mElementType = (localName.equalsIgnoreCase(ELEMENT_KEY_EXCEPTION))  ?  ELEMENT_MAPPING_EXCEPTION  :  // Map exception as 1.
            (localName.equalsIgnoreCase(ELEMENT_KEY_EXCEPTION_B))  ?  ELEMENT_MAPPING_EXCEPTION_B  :  // Map exception as 2.
              (localName.equalsIgnoreCase(ELEMENT_KEY_ID))    ?  ELEMENT_MAPPING_ID      :  // Map id as 3.
              (localName.equalsIgnoreCase(ELEMENT_KEY_FAILED))  ?  ELEMENT_MAPPING_FAILED    :  // Map failed as 4.
              (localName.equalsIgnoreCase(ELEMENT_KEY_MESSAGE))  ?  ELEMENT_MAPPING_MESSAGE    :  // Map message as 5.
                                          ELEMENT_MAPPING_DEFAULT;    // Map any other as 0.
      
      switch(mElementType) {
        case ELEMENT_MAPPING_EXCEPTION: {
          mFailed = true;
          Log.e(TAG, "ServiceException element found. Insert failed.");
          break;
        }
        case ELEMENT_MAPPING_EXCEPTION_B: {
          mFailed = true;
          Log.e(TAG, "Exception element found. Insert failed.");
          break;
        }
        case ELEMENT_MAPPING_FAILED: {
          Log.e(TAG, "Failed element found. Insert failed.");
          mFailed = true;
          break;
        }
        case ELEMENT_MAPPING_ID: {
          try {
            String fid = attributes.getValue("", "fid");
            String[] stringArray = fid.toString().split("[.]"); // Splits the input into sections.
            mInsertedIDs.add(Long.parseLong(stringArray[stringArray.length-1])); // Stores the last number in the ID array.
            
          } catch (NullPointerException e) { Log.e(TAG, "No such element attribute."); }          
          break;
        }
        default:
          break;
      }      
      super.startElement(uri, localName, qName, attributes);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
//      Log.d(TAG, "characters(char[], int, int) called.");
      switch(mElementType) {
        case ELEMENT_MAPPING_EXCEPTION: {
          throw new SAXException("ServiceException: " + new String(Utilities.copyOfRange_char(ch, start, length)));
        }
        case ELEMENT_MAPPING_EXCEPTION_B: {
          throw new SAXException("Exception: " + new String(Utilities.copyOfRange_char(ch, start, length)));
        }
        case ELEMENT_MAPPING_MESSAGE: {
          if(mFailed)
            throw new SAXException("ServiceException: " + new String(Utilities.copyOfRange_char(ch, start, length)));
          else
            Log.i(TAG, "Message: " + new String(Utilities.copyOfRange_char(ch, start, length)));
        }
      }
      super.characters(ch, start, length);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
//      Log.d(TAG, "endElement(String, String, String) called.");
      // Nothing to add.
      super.endElement(uri, localName, qName);
    }    

    @Override
    public void endDocument() throws SAXException {
//      Log.d(TAG, "endDocument() called: Finished examining Insert request response...");
      super.endDocument();
    }    
    
    @Override
    public void warning(SAXParseException e) throws SAXException {
      Log.w(TAG, "warning(SAXParseException) called: " + e.toString());
      super.warning(e);
    }

    @Override
    public void error(SAXParseException e) throws SAXException {
      Log.e(TAG, "error(SAXParseException) called: " + e.toString());
      super.error(e);
    }

    @Override
    public void fatalError(SAXParseException e) throws SAXException {
      Log.e(TAG, "fatalError(SAXParseException) called: " + e.toString());
      super.fatalError(e);
    }
  }
}




Java Source Code List

se.lu.nateko.edca.About.java
se.lu.nateko.edca.AttributeEditor.java
se.lu.nateko.edca.BackboneSvc.java
se.lu.nateko.edca.LayerViewer.java
se.lu.nateko.edca.MainMenu.java
se.lu.nateko.edca.MapViewer.java
se.lu.nateko.edca.ServerEditor.java
se.lu.nateko.edca.ServerViewer.java
se.lu.nateko.edca.Utilities.java
se.lu.nateko.edca.svc.DescribeFeatureType.java
se.lu.nateko.edca.svc.GeoHelper.java
se.lu.nateko.edca.svc.GeographyLayer.java
se.lu.nateko.edca.svc.GetCapabilities.java
se.lu.nateko.edca.svc.GetMap.java
se.lu.nateko.edca.svc.LocalSQLDBhelper.java
se.lu.nateko.edca.svc.ServerConnection.java
se.lu.nateko.edca.svc.UnclosableBufferedInputStream.java