Java tutorial
package se.lu.nateko.edca; import java.util.Date; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.DefaultHttpClient; import se.lu.nateko.edca.svc.DescribeFeatureType; import se.lu.nateko.edca.svc.GeoHelper; import se.lu.nateko.edca.svc.GeographyLayer; import se.lu.nateko.edca.svc.GetCapabilities; import se.lu.nateko.edca.svc.GetMap; import se.lu.nateko.edca.svc.LocalSQLDBhelper; import se.lu.nateko.edca.svc.ServerConnection; import android.app.Activity; import android.app.AlertDialog; import android.app.Service; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.os.Binder; import android.os.IBinder; import android.util.Log; import android.view.View; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.Toast; import com.google.android.gms.maps.model.LatLngBounds; import com.vividsolutions.jts.geom.GeometryFactory; /********************************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 * * * **************************************************************************** * General purpose Service, keeps track of the currently active * * ServerConnection, the active GeographyLayer, the HTTP connection and * * most of the other application-wide information. * * * * @author Mattias Spngmyr * * @version 0.95, 2013-08-05 * * * ****************************************************************************/ public class BackboneSvc extends Service { /** The error tag for this Service. */ public static final String TAG = "BackboneSvc"; /** String initialized in onCreate(), storing the application's package name for easy access. This is used to e.g. determine the folder path to the applications locally stored data. */ public static String PACKAGE_NAME; /** Constant identifying a non-specific Activity. */ public static final int ACTIVITY_DEFAULT = 0; /** Constant identifying the ServerEditor Activity. */ public static final int ACTIVITY_SERVEREDITOR = 1; /** Constant identifying the ServerViewer Activity. */ public static final int ACTIVITY_SERVERVIEWER = 2; /** Constant identifying the LayerViewer Activity. */ public static final int ACTIVITY_LAYERVIEWER = 3; /** Constant identifying the MapViewer Activity. */ public static final int ACTIVITY_MAPVIEWER = 4; /** Constant identifying the AttributeEditor Activity. */ public static final int ACTIVITY_ATTRIBUTEEDITOR = 5; /** Constant identifying that the Service is in a disconnected state, lacking an active ServerConnection. */ public static final int DISCONNECTED = 0; /** Constant identifying that the Service is in a connected state, with an active ServerConnection that has been reached. */ public static final int CONNECTED = 1; /** Constant identifying that the Service is in a connecting state, currently using an active ServerConnection to try to reach a server. */ public static final int CONNECTING = 2; /** Flag keeping track of the Service's state of connectedness to a server. DISCONNECTED by default. */ private int mConnectState = DISCONNECTED; /** Stores the row number in the local SQLite database which holds the server information currently being used, or most recently used, to connect to a server. */ private Long mConnectingRow = (long) 0; /** Whether or not an upload is currently underway. */ private boolean mUploading = false; /** Flag showing whether or not the user is currently allowed to select points for combining into sequences (lines or polygons). */ public boolean mCombining = false; /** Flag passed to renewLastServerConnection(boolean) to allow it to run. Will be changed to false after running once to ensure that the method is not run repeatedly without the user's request. */ public boolean mInitialRenewSrvConnection = true; /** Checked in renewActiveLayer(boolean) and changed to false after running the method to ensure that it is only run once. */ public boolean mInitialRenewLayer = true; /** The ServerConnection currently connected, or connecting, to. */ private ServerConnection mActiveServer; /** The GetCapabilities object most recently created, to call the active server to request a list of layers and capabilities and to parse and report the response. */ private GetCapabilities mActiveCapabilities; /** The GetMap object most recently created, to call the active server to request a map image and pass on the response. */ private GetMap mActiveGetMap; /** The DescribeFeatureType task most recently created, which requests information about a layer on the geospatial server and reports it to create a new GeographyLayer. */ private DescribeFeatureType mDescribeFeatureType; /** The GeoHelper most recently created, which handles saving to and loading from the external storage (e.g. sd-card) and uploading of data to the geospatial server. */ private GeoHelper mGeoHelper; /** The active GeographyLayer, holding information about the layer being targeted for data collection as well as holding any collected data waiting for upload. */ private GeographyLayer mActiveLayer; /** The current foreground Activity. Set in the onBound() method of each Activity. */ private Activity mActiveActivity; /** The helper class used to access and edit the local SQLite database. */ private LocalSQLDBhelper mSQLhelper; /** A JTS 1.8 GeometryFactory that is kept for the lifetime of the application. It is used to create JTS 1.8 Geometry objects that wrap around coordinates. */ private GeometryFactory mGeomFac = new GeometryFactory(); /** An application-wide RotateAnimation that ensures that the symbol indicating communication with the geospatial server is animated similarly in each Activity. */ private RotateAnimation mConnectionAnimation; /** Whether or not a thread is currently altering the RotateAnimation. */ private boolean mAlteringAnimation = false; /** Constant defining the time it should take (in milliseconds) the RotateAnimation to complete one spin. */ public static final int ROTATION_DURATION = 2000; /** Application-wide HttpClient stored for re-use. */ private HttpClient mHttpClient = new DefaultHttpClient(); /** Whether or not a thread is currently using the HttpClient to communicate with a server. */ private boolean mHttpConnecting = false; /** Binder given to client Activities in order to access this Service. */ private final IBinder mBinder = new SvcAccessor(this); @Override public void onCreate() { Log.d(TAG, "onCreate() called."); PACKAGE_NAME = getPackageName(); /* Setup the animation which shows when a connection to the geospatial server is active. */ mConnectionAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0.5); mConnectionAnimation.setDuration(ROTATION_DURATION); mConnectionAnimation.setInterpolator(this, android.R.anim.linear_interpolator); mConnectionAnimation.setFillAfter(true); mConnectionAnimation.setRepeatCount(0); mSQLhelper = new LocalSQLDBhelper(this); mSQLhelper.open(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { // Log.d(TAG, "onBind(Intent) called."); return mBinder; // Returns the specified interface. } @Override public void onDestroy() { Log.d(TAG, "onDestroy() called."); mSQLhelper.close(); /* Cancel any running ASyncTasks managed by the BackboneSvc. */ if (getActiveCapabilities() != null) getActiveCapabilities().cancel(true); if (getActiveGetMap() != null) getActiveGetMap().cancel(true); if (mDescribeFeatureType != null) mDescribeFeatureType.cancel(true); if (mGeoHelper != null) mGeoHelper.cancel(true); super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand(Intent, int, int) called."); return super.onStartCommand(intent, flags, startId); } /** * Set method to assign a ServerConnection object to be the currently active connection, * and tries to connect with a getCapabilities request. * @param srv The ServerConnection to be in use. */ public void setActiveServer(ServerConnection srv) { // Log.d(TAG, "setActiveServer(ServerConnection) called."); try { Log.i(TAG, "New active server '" + srv.getAddress()); mActiveServer = srv; } catch (NullPointerException e) { Log.w(TAG, e.toString()); mActiveServer = null; } } /** * Get method for retrieving the currently active ServerConnection object * from the BackboneSvc. * @return The active ServerConnection object. */ public ServerConnection getActiveServer() { // Log.d(TAG, "getActiveServer() called."); return mActiveServer; } /** * Stores the GetCapabilities object relating to the active ServerConnection. * @param getCap The active GetCapabilities object to set for this BackboneSvc. */ public void setActiveCapabilities(GetCapabilities getCap) { // Log.d(TAG, "setActiveCapabilities(GetCapabilities) called."); mActiveCapabilities = getCap; } /** * Get method for retrieving the stored GetCapabilities object * relating to the active ServerConnection from the BackboneSvc. * @return The GetCapabilities object of the active ServerConnection. */ public GetCapabilities getActiveCapabilities() { // Log.d(TAG, "getActiveCapabilities() called."); return mActiveCapabilities; } /** * Stores the GetMap object last created by the MapViewer. * @param getMap The active GetMap object to set for this BackboneSvc. */ public void setActiveGetMap(GetMap getMap) { // Log.d(TAG, "setActiveGetMap(GetMap) called."); mActiveGetMap = getMap; } /** * Get method for retrieving the GetMap object last * created by the MapViewer from the BackboneSvc. * @return The stored GetMap object. */ public GetMap getActiveGetMap() { // Log.d(TAG, "getActiveGetMap() called."); return mActiveGetMap; } /** * Method that sets the reference to the currently active Activity to which * results and alerts are posted. * @param activeActivity The Activity which is currently visible. */ public void setActiveActivity(Activity activeActivity) { // Log.d(TAG, "setActiveActivity(Activity) called."); mActiveActivity = activeActivity; Log.v(TAG, "New active activity: " + activeActivity.getLocalClassName()); } /** * Method that gets the reference to the currently active Activity to which * results and alerts are posted. * @return The Activity which is currently visible. */ public Activity getActiveActivity() { // Log.d(TAG, "getActiveActivity() called."); return mActiveActivity; } /** * Set method for the currently active local layer. * @param layer The layer to activate. */ public void setActiveLayer(GeographyLayer layer) { // Log.d(TAG, "setActiveLayer(GeographyLayer) called."); mActiveLayer = layer; if (layer != null) Log.v(TAG, "New active layer: " + mActiveLayer.getName()); else Log.v(TAG, "Active layer set to null."); } /** * Get method for the currently active local layer. * @return The currently active local layer. */ public GeographyLayer getActiveLayer() { // Log.d(TAG, "getActiveLayer() called."); return mActiveLayer; } /** * Fetches the application-wide HttpClient instance. * Can not be fetched again until unlockHttpClient() is called. * Threads calling this method before unlockHttpClient() has * been called will wait indefinitely in a spin-loop. * @return The HttpClient * @throws InterruptedException */ public synchronized HttpClient getHttpClient() throws InterruptedException { // Log.d(TAG, "getHttpClient() called."); while (isHttpConnecting()) { Log.v(TAG, "Thread " + Thread.currentThread().getId() + " waiting to fetch the HttpClient."); wait(); } mHttpConnecting = true; Log.v(TAG, "HttpClient given to thread: " + Thread.currentThread().getId()); return mHttpClient; } /** * Sets whether or not an HTTP connection is ongoing, to prevent * multiple uses of the HttpClient concurrently. */ public synchronized void unlockHttpClient() { // Log.d(TAG, "unlockHttpClient() called."); mHttpConnecting = false; // Log.v(TAG, "HttpClient unlocked by: " + Thread.currentThread().getId()); notify(); } /** * Fetches the flag showing whether or not an HTTP connection is ongoing. * @return True if an HTTP connection is ongoing. */ private boolean isHttpConnecting() { // Log.d(TAG, "isHttpConnecting() called."); return mHttpConnecting; } /** * Should be called whenever server communication fails, requiring * the user to reconnect. Clears the active ServerConnection and * sets the state to disconnected. Also clears remote layers that * can no longer be accessed. * @param reportFailure True to show a dialog alerting the user of a failed connection attempt. */ public void clearConnection(boolean reportFailure) { Log.d(TAG, "clearConnection(reportFailure=" + String.valueOf(reportFailure) + ") called."); /* Clear the active server since it cannot be contacted and notify the user. */ setActiveServer(null); setConnectState(BackboneSvc.DISCONNECTED, 0); clearRemoteLayers(); updateLayoutOnState(); if (reportFailure) showAlertDialog(getString(R.string.service_connectionfailed), null); } /** * Sets the value of the connect state flag * and the connecting row. * Can be DISCONNECTED (0), CONNECTED (1) or CONNECTING (2). * @param state The state to set. * @param row The row that is Connected to, or Connecting to. Pass 0 if not connected to any server. */ public void setConnectState(int state, long row) { Log.v(TAG, "setConnectState(state=" + String.valueOf(state) + ", row=" + String.valueOf(row) + ") called."); mConnectState = state; mConnectingRow = row; } /** * Gets the connect state of the BackboneSvc. * Can be DISCONNECTED (0), CONNECTED (1) or CONNECTING (2). * @return The connect state. */ public int getConnectState() { // Log.d(TAG, "getConnectState() called."); return mConnectState; } /** * Get method for the row (in the database table) that is currently being connected to. * @return The row being connected to. */ public Long getConnectingRow() { // Log.d(TAG, "getConnectingRow() called."); return mConnectingRow; } /** Start the Service's RotateAnimation. */ public void startAnimation() { // Log.d(TAG, "startAnimation() called."); /* Start the animation in a separate thread to avoid blocking more crucial threads. */ new Thread(new Runnable() { public void run() { try { Log.v(TAG, "Thread " + Thread.currentThread().getId() + " starting animation."); getAnimation().setRepeatCount(Animation.INFINITE); // Start the animation showing that a web communicating thread is active. unlockAnimation(); } catch (InterruptedException e) { Log.w(TAG, "Thread " + Thread.currentThread().getId() + " interrupted. " + e.toString()); } } }).start(); } /** Stop the Service's RotateAnimation. */ public void stopAnimation() { // Log.d(TAG, "stopAnimation() called."); /* Stop the animation in a separate thread to avoid blocking more crucial threads. */ new Thread(new Runnable() { public void run() { try { Log.v(TAG, "Thread " + Thread.currentThread().getId() + " stopping animation."); getAnimation().setRepeatCount(0); // Stop the animation showing that a web communicating thread has finished. unlockAnimation(); } catch (InterruptedException e) { Log.w(TAG, "Thread " + Thread.currentThread().getId() + " interrupted. " + e.toString()); } } }).start(); } /** * Fetches the application-wide rotation animation instance. * Can not be fetched using this method again until unlockAnimation() * is called. Threads calling this method before unlockAnimation() has * been called will wait indefinitely in a spin-loop. * @return The stored animation. * @throws InterruptedException */ public synchronized RotateAnimation getAnimation() throws InterruptedException { // Log.d(TAG, "getAnimation() called by thread: " + Thread.currentThread().getId()); while (getAlteringAnimation()) { Log.i(TAG, "Thread " + Thread.currentThread().getId() + " waiting to fetch the RotateAnimation."); wait(); } mAlteringAnimation = true; // Log.v(TAG, "RotateAnimation given to: " + Thread.currentThread().getId()); return mConnectionAnimation; } /** * Gets the RotateAnimation in a non-thread safe way, * and should thus not be used to alter the animation in any way. * @return The application-wide RotateAnimation. */ public RotateAnimation getAnimationNoQueue() { // Log.d(TAG, "getAnimationNoQueue() called."); return mConnectionAnimation; } /** * Indicates that alteration of the animation is finished, allowing * the first thread waiting to access the animation to proceed. */ public synchronized void unlockAnimation() { // Log.d(TAG, "unlockAnimation() called."); mAlteringAnimation = false; // Log.v(TAG, "RotateAnimation unlocked by: " + Thread.currentThread().getId()); notify(); } /** * Fetches the flag showing whether or not the RotateAnimation is being altered. * @return True if a thread is altering the animation. */ private boolean getAlteringAnimation() { // Log.d(TAG, "getAlteringAnimation() called."); return mAlteringAnimation; } /** * Sets the flag indicating whether or not an upload process is underway. * @param uploading True if uploading. */ public void setUploading(boolean uploading) { // Log.d(TAG, "setUploading(uploading=" + String.valueOf(uploading) + ") called."); mUploading = uploading; } /** * Gets the flag indicating whether or not an upload process is underway. * @return True if uploading. */ public boolean getUploading() { // Log.d(TAG, "getUploading() called."); return mUploading; } /** * Get method for retrieving the local SQLite database helper * from the BackboneSvc. * @return The active ServerConnection object. */ public LocalSQLDBhelper getSQLhelper() { // Log.d(TAG, "getSQLhelper() called."); return mSQLhelper; } /** * Gets the GeometryFactory which handles creation of * geometries from coordinates using a specified precision * model. * @return The GeometryFactory. */ public GeometryFactory getGeometryFactory() { // Log.d(TAG, "getGeometryFactory() called."); return mGeomFac; } /** * Deactivates all layers by removing the "active" factor from * the layer mode. */ public void deactivateLayers() { Log.d(TAG, "deactivateLayers() called."); Cursor activeLayers = getSQLhelper().fetchData(LocalSQLDBhelper.TABLE_LAYER, LocalSQLDBhelper.KEY_LAYER_COLUMNS, LocalSQLDBhelper.ALL_RECORDS, null, true); getActiveActivity().startManagingCursor(activeLayers); while (activeLayers.moveToNext()) { // As long as there's another row: if (activeLayers.getInt(2) % LocalSQLDBhelper.LAYER_MODE_ACTIVE == 0) {// If the layer is set to "active", then deactivate; getSQLhelper().updateData(LocalSQLDBhelper.TABLE_LAYER, activeLayers.getLong(0), LocalSQLDBhelper.KEY_LAYER_ID, new String[] { LocalSQLDBhelper.KEY_LAYER_USEMODE }, new String[] { String.valueOf(activeLayers.getInt(2) / LocalSQLDBhelper.LAYER_MODE_ACTIVE) }); } } } /** * Remove all layers that are not active or stored from the layer table, * e.g. because they can no longer be accessed. */ public void clearRemoteLayers() { Log.d(TAG, "clearRemoteLayers() called."); getSQLhelper().deleteData(LocalSQLDBhelper.TABLE_LAYER, LocalSQLDBhelper.KEY_LAYER_ID, LocalSQLDBhelper.ALL_RECORDS, LocalSQLDBhelper.KEY_LAYER_USEMODE + " % " + LocalSQLDBhelper.LAYER_MODE_STORE + "<> 0" + " AND " + LocalSQLDBhelper.KEY_LAYER_USEMODE + " % " + LocalSQLDBhelper.LAYER_MODE_ACTIVE + "<> 0"); } /** * A method that checks which Activity is currently active and updates * its layout according to the current state of the server connection, * the active layer, and the map. */ public void updateLayoutOnState() { /* Record which is the active Activity as an int flag (for the switch). */ String activity = getActiveActivity().getLocalClassName(); Log.v(TAG, "updateLayoutOnState() run for: " + activity); int activityFlag = (activity.equalsIgnoreCase("ServerEditor")) ? ACTIVITY_SERVEREDITOR : (activity.equalsIgnoreCase("ServerViewer")) ? ACTIVITY_SERVERVIEWER : (activity.equalsIgnoreCase("LayerViewer")) ? ACTIVITY_LAYERVIEWER : (activity.equalsIgnoreCase("MapViewer")) ? ACTIVITY_MAPVIEWER : (activity.equalsIgnoreCase("AttributeEditor")) ? ACTIVITY_ATTRIBUTEEDITOR : ACTIVITY_DEFAULT; switch (activityFlag) { case ACTIVITY_SERVEREDITOR: { ServerEditor srvEditor = (ServerEditor) getActiveActivity(); srvEditor.updateLayout(null); break; } case ACTIVITY_SERVERVIEWER: { ServerViewer srvViewer = (ServerViewer) getActiveActivity(); srvViewer.setLayout_ActiveServer(getActiveServer()); // Show the currently active ServerConnection name in green. srvViewer.setLayout_RenewButton(getConnectState()); // Disable the renew button while a connection attempt is ongoing. break; } case ACTIVITY_LAYERVIEWER: { LayerViewer lv = (LayerViewer) getActiveActivity(); lv.setLayout_ActiveLayer(getActiveLayer()); lv.populateLayerList(); lv.setLayout_UploadButton((!getUploading() && getConnectState() != CONNECTING), getUploading()); // Don't allow upload action during another upload or during a server connection attempt. /* * By sending the code in "action" to the runOnUiThread() method from a separate thread, * its code will be placed in the UI Thread Message queue and thus happen after other * queued messages (such as displaying the layout). */ new Thread(new Runnable() { public void run() { Runnable action = new Runnable() { public void run() { /* The following is put on the Message queue. */ ((LayerViewer) getActiveActivity()).setLayout_ListItems(); } }; ((LayerViewer) getActiveActivity()).runOnUiThread(action); } }).start(); break; } case ACTIVITY_MAPVIEWER: { MapViewer mapViewer = (MapViewer) getActiveActivity(); if (getActiveGetMap() != null) { if (getActiveGetMap().getImage() == null) Log.w(TAG, "No image stored by the active GetMap request."); mapViewer.showMapImage(getActiveGetMap().getImage()); // Display map image. } break; } case ACTIVITY_ATTRIBUTEEDITOR: { // Nothing to update. break; } default: break; } } /** * Method that tries to make a GetCapabilities request to the most recently * used server connection if there is one. * @param allow Whether or not to allow the renew. Used with a flag to prevent multiple calls on application start-up. */ public void renewLastSrvConnection(boolean allow) { Log.d(TAG, "renewLastSrvConnection() called. First time: " + String.valueOf(allow)); if (allow) { mInitialRenewSrvConnection = false; // Flag the action so it will not be executed again. /* Fetch the information on the last server connection used. */ Cursor lastSrv = mSQLhelper.fetchData(LocalSQLDBhelper.TABLE_SRV, LocalSQLDBhelper.KEY_SRV_COLUMNS, LocalSQLDBhelper.RECENT_RECORD, null, false); getActiveActivity().startManagingCursor(lastSrv); /* If a valid server connection record was found, make a new GetCapabilities request which, if successful, sets a new active server. */ if (lastSrv.moveToFirst() == false || lastSrv.getString(1).contentEquals(this.getString(R.string.srvedit_content_nolastuse))) { setActiveServer(null); Log.i(TAG, "No active server connection could be renewed."); } else makeGetCapabilitiesRequest(new ServerConnection(lastSrv.getLong(0), Utilities.DATE_LONG.format(new Date()), lastSrv.getString(2), lastSrv.getString(3), lastSrv.getString(4), lastSrv.getString(5), lastSrv.getString(6), lastSrv.getString(7), lastSrv.getInt(8))); } } /** * Try to load a Layer from the stored information * in the local SQLite database and on the external * storage of the device. * @param allow Whether or not to allow the renew. Used with a flag to prevent multiple calls on application start-up. */ public void renewActiveLayer(boolean allow) { Log.d(TAG, "renewActiveLayer() called. First time: " + String.valueOf(mInitialRenewLayer)); if (allow) { mInitialRenewLayer = false; // Flag the action so it will not be executed again. /* Find the active layer if there is one and load it. */ Cursor layerCursor = getSQLhelper().fetchData(LocalSQLDBhelper.TABLE_LAYER, LocalSQLDBhelper.KEY_LAYER_COLUMNS, LocalSQLDBhelper.ALL_RECORDS, LocalSQLDBhelper.KEY_LAYER_USEMODE + " % " + LocalSQLDBhelper.LAYER_MODE_ACTIVE + " = 0", true); getActiveActivity().startManagingCursor(layerCursor); if (layerCursor.moveToFirst()) { // If there is an active layer: if (layerCursor.getInt(2) % LocalSQLDBhelper.LAYER_MODE_STORE == 0) { // If the layer is stored on the device; load it locally. Log.i(TAG, "Loading active layer from local storage..."); setActiveLayer(generateGeographyLayer(layerCursor.getString(1))); makeLoadOperation(layerCursor.getInt(0)); } else {// Else, try to load it from the geospatial server. Log.i(TAG, "Loading active layer from geospatial server..."); makeDescribeFeatureTypeRequest(getActiveServer(), layerCursor.getString(1), layerCursor.getInt(0)); } } else Log.i(TAG, "No active layer found."); } } /** * Method that initiates a GetCapabilities request on a separate thread. * @param srv The ServerConnection to use for the request. */ public void makeGetCapabilitiesRequest(ServerConnection srv) { Log.d(TAG, "makeGetCapabilitiesRequest(ServerConnection=" + srv.getName() + ") called."); /* Record the new connect state and which server is being connected to. */ setConnectState(CONNECTING, srv.getID()); startAnimation(); // Start the animation, showing that a web communicating thread is active updateLayoutOnState(); /* * Start a separate Thread in which a getCapabilities request is created * and the input ServerConnection is set as the new ActiveConnection if the * server responds. If there is already a GetCapabilities thread running, * kill it before starting the new one. */ if (getActiveCapabilities() != null) getActiveCapabilities().cancel(true); setActiveCapabilities(new GetCapabilities(this)); getActiveCapabilities().execute(srv); } /** * Method that initiates a GetMap request on a separate thread. * @param srv The ServerConnection to use for the request. * @param layout A layout with the same dimensions as the view to fill with the image returned by the GetMap request. * @param bounds The LatLngBounds bounding box to use for the request, specifying what area the map should cover. */ public void makeGetMapRequest(ServerConnection srv, View layout, LatLngBounds bounds) { Log.d(TAG, "makeGetMapRequest(ServerConnection=" + srv.getName() + ", View, BBox) called."); /* * Start a separate Thread in which a getMap request is created. * If there is already a GetCapabilities thread running, kill it * before starting the new one. */ if (getActiveGetMap() != null) getActiveGetMap().cancel(true); setActiveGetMap(new GetMap(this, layout.getWidth(), layout.getHeight(), bounds)); getActiveGetMap().execute(srv); } /** * Method that initiates a DescribeFeatureType request on a separate thread. * @param srv The ServerConnection to use for the request. * @param layerName The layer whose attribute information to request. * @param rowId The row ID of the layer in the local SQLite database. */ public void makeDescribeFeatureTypeRequest(ServerConnection srv, String layerName, long rowId) { Log.d(TAG, "makeDescribeFeatureTypeRequest(ServerConnection=" + srv.getName() + ", layerName=" + layerName + ", rowId=" + String.valueOf(rowId) + ") called."); /* Report that a server is being contacted. */ setConnectState(CONNECTING, srv.getID()); startAnimation(); // Start the animation, showing that a web communicating thread is active. updateLayoutOnState(); /* Start the request on a separate thread (ASyncTask). */ mDescribeFeatureType = new DescribeFeatureType(this, layerName, rowId); mDescribeFeatureType.execute(srv); } /** * Starts the process of storing the active layer's data on * a separate thread, on the geospatial server or locally. * @param rwMode Operation mode. "Upload", "Write" or "Overwrite". */ public void makeSaveOperation(int rwMode) { Log.d(TAG, "makeSaveOperation(rwMode=" + rwMode + ") called."); mGeoHelper = new GeoHelper(this, rwMode); mGeoHelper.execute(getActiveLayer()); } /** * Starts the process of loading a locally stored layer into * the active layer on a separate thread. * @param rowId The rowId in the Layer table of the layer to activate. */ public void makeLoadOperation(long rowId) { Log.d(TAG, "makeLoadOperation(rowId=" + String.valueOf(rowId) + ") called."); mGeoHelper = new GeoHelper(this, GeoHelper.RWMODE_READ, rowId); mGeoHelper.execute(getActiveLayer()); } /** * Generates a GeographyLayer object with attributes, ready for * adding geometry. * * @param layerName The name of the layer to generate. * @return A geometry-less GeographyLayer with its attributes loaded. */ public GeographyLayer generateGeographyLayer(String layerName) { Log.d(TAG, "generateGeographyLayer(layerName=" + layerName + ") called."); GeographyLayer layer = null; /* Find out the type of the layer. */ Cursor geomTypeCursor = getSQLhelper().fetchData( LocalSQLDBhelper.TABLE_FIELD_PREFIX + Utilities.dropColons(layerName, Utilities.RETURN_LAST), new String[] { LocalSQLDBhelper.KEY_FIELD_NAME, LocalSQLDBhelper.KEY_FIELD_DATATYPE }, LocalSQLDBhelper.ALL_RECORDS, LocalSQLDBhelper.KEY_FIELD_NAME + " LIKE('%_geom')", true); getActiveActivity().startManagingCursor(geomTypeCursor); if (geomTypeCursor.moveToFirst()) { if (GeographyLayer.getGeometryType(geomTypeCursor.getString(1)) != GeographyLayer.TYPE_INVALID) layer = new GeographyLayer(this, layerName, geomTypeCursor.getString(1)); else { Log.e(TAG, "Invalid geometry type."); return null; } } else { Log.e(TAG, "No '_geom' field found."); return null; } /* Load the layer's fields. */ Cursor fieldCursor = getSQLhelper().fetchData( LocalSQLDBhelper.TABLE_FIELD_PREFIX + Utilities.dropColons(layerName, Utilities.RETURN_LAST), LocalSQLDBhelper.KEY_FIELD_COLUMNS, LocalSQLDBhelper.ALL_RECORDS, null, true); getActiveActivity().startManagingCursor(fieldCursor); if (fieldCursor.moveToFirst()) { layer.addField(fieldCursor.getString(1), Boolean.parseBoolean(fieldCursor.getString(2)), fieldCursor.getString(3)); while (fieldCursor.moveToNext()) layer.addField(fieldCursor.getString(1), Boolean.parseBoolean(fieldCursor.getString(2)), fieldCursor.getString(3)); } else Log.i(TAG, "No fields found for this layer."); return layer; } /** * Method that displays an alert dialog to the user showing the string argument as the message text. * @param message Message to display in the alert dialog. * @param target The activity to display the AlertDialog in, pass null to default to the "active" Activity. */ public void showAlertDialog(String message, Activity target) { Log.d(TAG, "showAlertDialog(String) called."); final String msg = message; final Activity tg = target; /* * By sending the code in "action" to the runOnUiThread() method from a separate thread, * its code will be placed in the UI Thread Message queue and thus happen after other * queued messages (such as displaying the layout). */ new Thread(new Runnable() { public void run() { Runnable action = new Runnable() { public void run() { /* The following is put on the Message queue. */ AlertDialog alertDialog = new AlertDialog.Builder((tg == null) ? getActiveActivity() : tg) .create(); alertDialog.setMessage(msg); /* Add a button to the dialog and set its text and button listener. */ alertDialog.setButton( alertDialog.getContext().getString(R.string.service_alert_buttontext_ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alertDialog.show(); // Display the dialog to the user. } }; ((tg == null) ? getActiveActivity() : tg).runOnUiThread(action); } }).start(); } /** * Method that displays an on screen text (Toast) to the user showing * the string resource argument as the message text. * @param message Resource ID of a string resource holding the message to display in the Toast. */ public void showToast(int message) { Log.d(TAG, "showToast(String) called."); final int msg = message; /* Show the Toast on the UI thread. */ Runnable action = new Runnable() { public void run() { Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show(); } }; getActiveActivity().runOnUiThread(action); } /** * A local implementation of Binder that simply lets the calling * Activity fetch the Service reference. * * @author Mattias Spngmyr * @version 0.10, 2013-07-16 */ public class SvcAccessor extends Binder { /* The error tag for this Binder. */ // private String TAG = "BackboneSvc.SvcAccessor"; /** A reference to the application's background Service, received in the constructor. */ public final BackboneSvc mService; /** * Default constructor setting the reference to the BackboneSvc Service. * @param svc The BackboneSvc Service to store a reference to. */ public SvcAccessor(BackboneSvc svc) { // Log.d(TAG, "SvcAccessor(BackboneSvc) constructor called."); mService = svc; } } }