public interface IGISApplication
{
    /**
     * @return A MapBase or any inherited classes or null if not created in application
     */
    MapBase getMap();

    /**
     * @return A authority for sync purposes or empty string if not sync anything
     */
    String getAuthority();

    /**
     * Add account to android account storage
     * @param name Account name (must be uniq)
     * @param url NextGIS Web Server URL
     * @param login User login
     * @param password User password
     * @param token A token returned from NextGIS Web Server (may be empty string)
     * @return true on success or false
     */
    boolean addAccount(String name, String url, String login, String password, String token);

    /**
     * Update account information
     * @param name Account name (the account must be exist)
     * @param key The account key to change (i.e. URL)
     * @param value The new value for key
     */
    void setUserData(String name, String key, String value);

    /**
     * Change password for account
     * @param name Account name (the account must be exist)
     * @param value New password
     */
    void setPassword(String name,String value);

    /**
     * @param accountName Account name
     * @return Account by its name
     */
    Account getAccount(String accountName);

    /**
     * Remove an account
     * @param account Account to remove
     * @return An @see AccountManagerFuture which resolves to a Boolean, true if the account has been successfully removed
     */
    AccountManagerFuture<Boolean> removeAccount(Account account);

    /**
     * @param account Account object
     * @return Account URL
     */
    String getAccountUrl(Account account);

    /**
     * Return some account data
     * @param account account object
     * @param key key to return
     * @return value in user key - value map
     */
    String getAccountUserData(Account account, String key);

    /**
     * @param account Account object
     * @return Account login
     */
    String getAccountLogin(Account account);

    /**
     * @param account Account object
     * @return Account password
     */
    String getAccountPassword(Account account);

    /**
     * @return A GpsEventSource or null if not needed or created in application
     */
    GpsEventSource getGpsEventSource();

    /**
     * Show settings Activity or nothing
     */
    void showSettings(String setting);

    /**
     * Send target event to analytics
     */
    void sendEvent(String category, String action, String label);

    /**
     * Send screen hit to analytics
     */
    void sendScreen(String name);

    /**
     * Get accounts authenticator type
     */
    String getAccountsType();

    /**
     * Get LayerFactory
     */
    LayerFactory getLayerFactory();
} public interface INGWLayer
{
    /**
     * Return the account connected with this layer
     * @return account name
     */
    String getAccountName();

    /**
     * Set the account connected with this layer
     * @param account Name
     */
    void setAccountName(String account);

    /**
     * cache account data for fast access
     */
    void setAccountCacheData();

    /**
     * Sync layer data with NextGIS Web
     * @param authority the account authority
     * @param syncResult object for storing results of operations
     */
    void sync(String authority, Pair<Integer, Integer> ver, SyncResult syncResult);

    /**
     * Return the sync type (i.e. sync only attributes or the whole feature)
     * @return
     */
    int getSyncType();

    /**
     * Set sync type.
     * @see com.nextgis.maplib.util.Constants#SYNC_ALL
     * @param syncType The value result of OR different sync types
     */
    void setSyncType(int syncType);

    /**
     * Return the NextGIS Web server identificator - uniq identificator set for layer on server.
     * @return The NextGIS Web server identificator
     */
    long getRemoteId();

    /**
     * Set NextGIS Web server identificator.
     * @param remoteId The NextGIS Web server identificator
     */
    void setRemoteId(long remoteId);
} public interface IProgressor {
    /**
     * Set maximum value of progress
     * @param maxValue maximum value
     */
    void setMax(int maxValue);

    /**
     * The process should execute this method if it should be canceled.
     * @return true if progress must be canceled, or false
     */
    boolean isCanceled();

    /**
     * Set current progress.
     * @param value current progress
     */
    void setValue(int value);

    /**
     * If progress cannot determinate the max value and current value, it can be set as indeterminate.
     * @param indeterminate true to set indeterminate state, or false
     */
    void setIndeterminate(boolean indeterminate);

    /**
     * Set some message to show in progress dialog or somewhere else.
     * @param message a message
     */
    void setMessage(String message);
} public class Feature
        implements IJSONStore
{
    protected long                    mId;
    protected GeoGeometry             mGeometry;
    protected List<Object>            mFieldValues;
    protected List<Field>             mFields;
    protected Map<String, AttachItem> mAttachments;


    public Feature()
    {
        mId = Constants.NOT_FOUND;
        mFields = new ArrayList<>();
        mFieldValues = new ArrayList<>();
        mAttachments = new HashMap<>();
    }


    public Feature(
            long id,
            List<Field> fields)
    {
        mId = id;
        mFields = fields;
        mFieldValues = new ArrayList<>(fields.size());
        mAttachments = new HashMap<>();
    }


    public Feature(Feature other)
    {
        mId = other.getId();
        mGeometry = other.getGeometry();
        mFields = other.getFields();
        mFieldValues = other.getFieldValues();
        mAttachments = other.getAttachments();
    }


    public long getId()
    {
        return mId;
    }


    public void setId(long id)
    {
        mId = id;
    }


    public void setGeometry(GeoGeometry geometry)
    {
        mGeometry = geometry;
    }


    public GeoGeometry getGeometry()
    {
        return mGeometry;
    }


    public boolean setFieldValue(
            int index,
            Object value)
    {
        if (index < 0 || index >= mFields.size()) {
            return false;
        }
        if (mFieldValues.size() <= index) {
            for (int i = mFieldValues.size(); i <= index; i++) {
                mFieldValues.add(null);
            }
        }
        mFieldValues.set(index, value);
        return true;
    }


    public boolean setFieldValue(
            String fieldName,
            Object value)
    {
        int index = getFieldValueIndex(fieldName);
        return setFieldValue(index, value);
    }


    public boolean isValuePresent(int index)
    {
        return getFieldValue(index) != null;
    }


    public Object getFieldValue(int index)
    {
        if (mFields.isEmpty() || mFieldValues.isEmpty() || index < 0 || index >= mFields.size() ||
                index >= mFieldValues.size()) {
            return null;
        }
        return mFieldValues.get(index);
    }


    public Object getFieldValue(String fieldName)
    {
        int index = getFieldValueIndex(fieldName);
        return getFieldValue(index);
    }


    public int getFieldValueIndex(String fieldName)
    {
        for (int i = 0; i < mFields.size(); i++) {
            if (mFields.get(i).getName().equals(fieldName)) {
                return i;
            }
        }
        return NOT_FOUND;
    }


    public int getFieldValueAsInteger(String fieldName)
    {
        int index = getFieldValueIndex(fieldName);
        return getFieldValueAsInteger(index);
    }


    public int getFieldValueAsInteger(int index)
    {
        if (mFields.isEmpty() || mFieldValues.isEmpty() || index < 0 || index >= mFields.size() ||
                index >= mFieldValues.size()) {
            return Integer.MAX_VALUE;
        }

        Object val = mFieldValues.get(index);
        if (null == val) {
            return Integer.MAX_VALUE;
        }

        if (val instanceof Long) {
            Long lval = (Long) val;
            return lval.intValue();
        } else if (val instanceof Integer) {
            return (int) val;
        } else {
            return Integer.MAX_VALUE;
        }
    }


    public String getFieldValueAsString(String fieldName)
    {
        int index = getFieldValueIndex(fieldName);
        return getFieldValueAsString(index);
    }


    public String getFieldValueAsString(int index)
    {
        if (mFields.isEmpty() || mFieldValues.isEmpty() || index < 0 || index >= mFields.size() ||
                index >= mFieldValues.size()) {
            return "";
        }
        Object val = mFieldValues.get(index);
        if (null == val) {
            return "";
        }
        switch (mFields.get(index).getType()) {
            case FTString:
            case FTReal:
            case FTInteger:
                return val.toString();
            case FTDate:
                if (val instanceof Long) {
                    DateFormat dateFormat = DateFormat.getDateInstance();
                    return dateFormat.format(new Date((Long) val));
                }
                break;
            case FTTime:
                if (val instanceof Long) {
                    DateFormat dateFormat = DateFormat.getTimeInstance();
                    return dateFormat.format(new Date((Long) val));
                }
                break;
            case FTDateTime:
                if (val instanceof Long) {
                    DateFormat dateFormat = DateFormat.getDateTimeInstance();
                    return dateFormat.format(new Date((Long) val));
                }
                break;
        }
        return "";
    }


    @Override
    public JSONObject toJSON()
            throws JSONException
    {
        JSONObject oJSONOut = new JSONObject();
        oJSONOut.put(GEOJSON_TYPE, GEOJSON_TYPE_Feature);
        oJSONOut.put(GEOJSON_FEATURE_ID, mId);
        oJSONOut.put(GEOJSON_GEOMETRY, mGeometry.toJSON());
        JSONObject oJSONProp = new JSONObject();
        for (int i = 0; i < mFieldValues.size(); i++) {
            String key = mFields.get(i).getName();
            oJSONProp.put(key, mFieldValues.get(i));
        }
        oJSONOut.put(GEOJSON_PROPERTIES, oJSONProp);
        return oJSONOut;
    }


    @Override
    public void fromJSON(JSONObject jsonObject)
            throws JSONException
    {
        if (!jsonObject.getString(GEOJSON_TYPE).equals(GEOJSON_TYPE_Feature)) {
            throw new JSONException("not valid geojson feature");
        }
        mId = jsonObject.getInt(GEOJSON_FEATURE_ID);
        JSONObject oJSONGeom = jsonObject.getJSONObject(GEOJSON_GEOMETRY);
        mGeometry = GeoGeometryFactory.fromJson(oJSONGeom);
        JSONObject jsonAttributes = jsonObject.getJSONObject(GEOJSON_PROPERTIES);
        Iterator<String> iter = jsonAttributes.keys();
        while (iter.hasNext()) {
            String key = iter.next();
            Object value = jsonAttributes.get(key);
            mFieldValues.add(value);
        }
    }


    public List<Field> getFields()
    {
        return mFields;
    }


    public List<Object> getFieldValues()
    {
        return mFieldValues;
    }


    public ContentValues getContentValues(boolean withId)
    {
        ContentValues values = new ContentValues();
        if (withId) {
            if (mId != Constants.NOT_FOUND) {
                values.put(Constants.FIELD_ID, mId);
            } else {
                values.putNull(Constants.FIELD_ID);
            }
        }

        try {
            if (null != mGeometry) {
                values.put(FIELD_GEOM, mGeometry.toBlob());
            }
        } catch (IOException e) { //if exception - not create geom
            e.printStackTrace();
        }
        if (!values.containsKey(FIELD_GEOM)) {
            values.putNull(FIELD_GEOM);
        }

        for (int i = 0; i < mFields.size(); i++) {
            Field field = mFields.get(i);

            if (!isValuePresent(i)) {
                values.putNull(field.getName());
                continue;
            }

            switch (field.getType()) {
                case FTString:
                    values.put(field.getName(), getFieldValueAsString(i));
                    break;

                case FTInteger:
                    Object intVal = getFieldValue(i);
                    if (intVal instanceof Integer) {
                        values.put(field.getName(), (int) intVal);
                    } else if (intVal instanceof Long) {
                        values.put(field.getName(), (long) intVal);
                    } else {
                        Log.d(TAG, "skip value: " + intVal.toString());
                    }
                    break;

                case FTReal:
                    Object realVal = getFieldValue(i);
                    if (realVal instanceof Double) {
                        values.put(field.getName(), (double) realVal);
                    } else if (realVal instanceof Float) {
                        values.put(field.getName(), (float) realVal);
                    } else {
                        Log.d(TAG, "skip value: " + realVal.toString());
                    }
                    break;

                case FTDate:
                case FTTime:
                case FTDateTime:
                    Object dateVal = getFieldValue(i);
                    if (dateVal instanceof Date) {
                        Date date = (Date) dateVal;
                        values.put(field.getName(), date.getTime());
                    } else if (dateVal instanceof Long) {
                        values.put(field.getName(), (long) dateVal);
                    } else if (dateVal instanceof Calendar) {
                        Calendar cal = (Calendar) dateVal;
                        values.put(field.getName(), cal.getTimeInMillis());
                    } else {
                        Log.d(TAG, "skip value: " + dateVal.toString());
                    }
                    break;
            }
        }

        return values;
    }


    public void fromCursor(Cursor cursor)
    {
        if (null == cursor) {
            return;
        }
        mId = cursor.getLong(cursor.getColumnIndex(FIELD_ID));

        try {
            mGeometry =
                    GeoGeometryFactory.fromBlob(cursor.getBlob(cursor.getColumnIndex(FIELD_GEOM)));
        } catch (IOException e) { //let it be empty geometry
            e.printStackTrace();
        }

        for (int i = 0; i < mFields.size(); i++) {
            Field field = mFields.get(i);
            int index = cursor.getColumnIndex(field.getName());
            if (cursor.isNull(index)) {
                setFieldValue(i, null);
            } else {
                if (index != NOT_FOUND) {
                    switch (field.getType()) {
                        case FTString:
                            setFieldValue(i, cursor.getString(index));
                            break;
                        case FTInteger:
                            setFieldValue(i, cursor.getLong(index));
                            break;
                        case FTReal:
                            setFieldValue(i, cursor.getDouble(index));
                            break;
                        case FTDate:
                        case FTTime:
                        case FTDateTime:
                            TimeZone timeZone = TimeZone.getDefault();
                            timeZone.setRawOffset(0); // set to UTC
                            Calendar calendar = Calendar.getInstance(timeZone);
                            calendar.setTimeInMillis(cursor.getLong(index));
                            setFieldValue(i, calendar.getTimeInMillis());
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }


    public boolean equalsData(Feature f)
    {
        if (null == f) {
            return false;
        }
        //compare attributes
        Log.d(TAG, "Feature id:" + mId + " compare attributes");
        for (int i = 0; i < mFields.size(); i++) {
            Field field = mFields.get(i);

            Object value = getFieldValue(i);
            Object valueOther = f.getFieldValue(field.getName());

            //Log.d(TAG, value + "<->" + valueOther);

            if (null == value) {
                if (null != valueOther) {
                    if (field.getType() == FTDateTime) {
                        long lValue = dateObjectToLong(valueOther);
                        if (lValue > 0) {
                            Log.d(TAG, value + "<->" + valueOther);
                            return false;
                        }
                    } else {
                        Log.d(TAG, value + "<->" + valueOther);
                        return false;
                    }
                }
            } else if (null == valueOther) {
                Log.d(TAG, value + "<->" + valueOther);
                return false;
            } else {
                if (field.getType() == GeoConstants.FTInteger) {
                    if (!checkIntegerEqual(value, valueOther)) {
                        Log.d(TAG, value + "<->" + valueOther);
                        return false;
                    }
                } else if (field.getType() == GeoConstants.FTReal) {
                    if (!checkRealEqual(value, valueOther)) {
                        Log.d(TAG, value + "<->" + valueOther);
                        return false;
                    }
                } else if (field.getType() == GeoConstants.FTDate ||
                        field.getType() == GeoConstants.FTTime ||
                        field.getType() == GeoConstants.FTDateTime) {
                    if (!checkDateEqual(value, valueOther)) {
                        Log.d(TAG, value + "<->" + valueOther);
                        return false;
                    }
                } else if (!value.equals(valueOther)) { // any other cases
                    Log.d(TAG, value + "<->" + valueOther);
                    return false;
                }
            }
        }

        //compare geometry
        Log.d(TAG, "Feature id:" + mId + " compare geometry");
        if (null == mGeometry) {
            return null == f.getGeometry();
        }
        return mGeometry.equals(f.getGeometry());
    }


    private long dateObjectToLong(Object value)
    {
        if (value instanceof Long) {
            return (Long) value;
        } else if (value instanceof Date) {
            Date date = (Date) value;
            return date.getTime();
        } else if (value instanceof Calendar) {
            Calendar cal = (Calendar) value;
            return cal.getTimeInMillis();
        }

        return Constants.NOT_FOUND;
    }


    private boolean checkDateEqual(
            Object value,
            Object valueOther)
    {
        return dateObjectToLong(value) == dateObjectToLong(valueOther);
    }


    private boolean checkRealEqual(
            Object value,
            Object valueOther)
    {
        if (value instanceof Float && valueOther instanceof Double) {
            Float vlong = (Float) value;
            Double ovlong = (Double) valueOther;
            return vlong == ovlong.floatValue();
        } else if (value instanceof Double && valueOther instanceof Float) {
            Double vlong = (Double) value;
            Float ovlong = (Float) valueOther;
            return vlong.floatValue() == ovlong;
        } else {
            return value.equals(valueOther);
        }
    }


    private boolean checkIntegerEqual(
            Object value,
            Object valueOther)
    {
        if (value instanceof Integer && valueOther instanceof Long) {
            Integer vlong = (Integer) value;
            Long ovlong = (Long) valueOther;
            return vlong == ovlong.intValue();
        } else if (value instanceof Long && valueOther instanceof Integer) {
            Long vlong = (Long) value;
            Integer ovlong = (Integer) valueOther;
            return vlong.intValue() == ovlong;
        } else {
            return value.equals(valueOther);
        }
    }


    public boolean equalsAttachments(Feature f)
    {
        if (null == f) {
            return false;
        }
        //compare attachments
        Map<String, AttachItem> attachments = f.getAttachments();
        if (mAttachments.size() != attachments.size()) {
            return false;
        }
        for (AttachItem item : mAttachments.values()) {
            AttachItem otherItem = attachments.get(item.getAttachId());
            if (null == otherItem) {
                return false;
            }
            if (!item.equals(otherItem)) {
                return false;
            }
        }
        return true;
    }


    @Override
    public boolean equals(Object o)
    {
        if (super.equals(o)) //if same pointers
        {
            return true;
        }

        Feature other = (Feature) o;
        // go deeper
        return equalsData(other) && equalsAttachments(other);
    }


    public void addAttachment(AttachItem item)
    {
        mAttachments.put(item.getAttachId(), item);
    }


    public void addAttachments(Map<String, AttachItem> attachments)
    {
        if (null == attachments) {
            return;
        }
        mAttachments = attachments;
    }


    public Map<String, AttachItem> getAttachments()
    {
        return mAttachments;
    }


    public void clearAttachments()
    {
        mAttachments.clear();
    }


    public long getMaxAttachId()
    {
        long maxAttachId = 0;

        if (null == mAttachments) {
            return maxAttachId;
        }

        for (String key : mAttachments.keySet()) {
            long keyL = Long.parseLong(key);
            maxAttachId = Math.max(maxAttachId, keyL);
        }

        return maxAttachId;
    }
} public class Field implements IJSONStore, Parcelable {
    protected int mType;
    protected String mName;
    protected String mAlias;

    protected static final String JSON_ALIAS_KEY = "alias";

    public Field() { }

    public Field(int type, String name, String alias) {
        mType = type;
        mName = name;
        if (TextUtils.isEmpty(alias)) {
            mAlias = mName;
        } else {
            mAlias = alias;
        }
    }

    private Field(Parcel in) {
        this();
        mType = in.readInt();
        mName = in.readString();
        mAlias = in.readString();
    }

    @Override
    public JSONObject toJSON() throws JSONException {
        JSONObject rootObject = new JSONObject();
        rootObject.put(Constants.JSON_TYPE_KEY, mType);
        rootObject.put(Constants.JSON_NAME_KEY, mName);
        rootObject.put(JSON_ALIAS_KEY, mAlias);
        return rootObject;
    }

    @Override
    public void fromJSON(JSONObject jsonObject) throws JSONException {
        mType = jsonObject.getInt(Constants.JSON_TYPE_KEY);
        mName = jsonObject.getString(Constants.JSON_NAME_KEY);
        if (jsonObject.has(JSON_ALIAS_KEY)) {
            mAlias = jsonObject.getString(JSON_ALIAS_KEY);
        }
    }

    public int getType() {
        return mType;
    }

    public String getName() {
        return mName;
    }

    public String getAlias() {
        return mAlias;
    }

    public void setName(String name) {
        mName = name;
    }

    public void setAlias(String alias) {
        mAlias = alias;
    }

    public static final Creator<Field> CREATOR = new Creator<Field>() {
        @Override
        public Field createFromParcel(Parcel in) {
            return new Field(in);
        }

        @Override
        public Field[] newArray(int size) {
            return new Field[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(mType);
        parcel.writeString(mName);
        parcel.writeString(mAlias);
    }
} public abstract class GeoGeometry
        implements Serializable
{
    protected static final long serialVersionUID = -1241179697270831761L;
    protected int mCRS;


    public boolean project(int toCrs)
    {
        return (mCRS == CRS_WGS84 && toCrs == CRS_WEB_MERCATOR ||
                mCRS == CRS_WEB_MERCATOR && toCrs == CRS_WGS84) && rawProject(toCrs);
    }


    protected boolean rawProject(int toCrs)
    {
        mCRS = toCrs;
        return true;
    }


    public abstract GeoEnvelope getEnvelope();


    public void setCRS(int crs)
    {
        mCRS = crs;
    }


    public int getCRS()
    {
        return mCRS;
    }


    public JSONObject toJSON()
            throws JSONException
    {
        JSONObject jsonOutObject = new JSONObject();
        jsonOutObject.put(GEOJSON_TYPE, typeToJSON());
        jsonOutObject.put(GEOJSON_COORDINATES, coordinatesToJSON());

        return jsonOutObject;
    }


    public String typeToJSON()
    {
        switch (getType()) {
            case GTPoint:
                return GEOJSON_TYPE_Point;
            case GTLineString:
                return GEOJSON_TYPE_LineString;
            case GTPolygon:
                return GEOJSON_TYPE_Polygon;
            case GTMultiPoint:
                return GEOJSON_TYPE_MultiPoint;
            case GTMultiLineString:
                return GEOJSON_TYPE_MultiLineString;
            case GTMultiPolygon:
                return GEOJSON_TYPE_MultiPolygon;
            case GTGeometryCollection:
                return GEOJSON_TYPE_GeometryCollection;
            case GTNone:
            default:
                return "";
        }
    }


    public abstract JSONArray coordinatesToJSON()
            throws JSONException, ClassCastException;


    public abstract int getType();


    public abstract void setCoordinatesFromJSON(JSONArray coordinates)
            throws JSONException;

    public abstract void setCoordinatesFromJSONStream(JsonReader reader, int crs)
            throws IOException;

    public abstract void setCoordinatesFromWKT(String wkt, int crs);


    public byte[] toBlobOld()
            throws IOException
    {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream os = new ObjectOutputStream(out);
        os.writeObject(this);
        return out.toByteArray();
    }


    public byte[] toBlob()
            throws IOException
    {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        DataOutputStream dataOutputStream = new DataOutputStream(out);
        write(dataOutputStream);
        return out.toByteArray();
    }


    public abstract String toWKT(boolean full);


    public boolean equals(Object o)
    {
        if (super.equals(o)) {
            return true;
        }
        GeoGeometry other = (GeoGeometry) o;
        return null != other && getType() == other.getType() && mCRS == other.getCRS();
    }


    public boolean intersects(GeoEnvelope envelope)
    {
        return getEnvelope().intersects(envelope);
    }


    /**
     * Make deep copy of geometry
     *
     * @return The geometry copy
     */
    public abstract GeoGeometry copy();

    /**
     * remove all points from geometry
     */
    public abstract void clear();

    public abstract GeoGeometry simplify(double tolerance);

    public abstract GeoGeometry clip(GeoEnvelope envelope);

    public void write(DataOutputStream stream) throws IOException{
        stream.writeInt(getType());
        stream.writeInt(mCRS);
    }

    public void read(DataInputStream stream) throws IOException{
        mCRS = stream.readInt();
    }

    public abstract boolean isValid();

    public abstract double distance(GeoGeometry geometry);
} public class GeoGeometryFactory
{
    public static GeoGeometry fromJsonStream(JsonReader reader, int crs) throws IOException {
        GeoGeometry geometry = null;
        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if(name.equalsIgnoreCase(GeoConstants.GEOJSON_TYPE)){
                int type = typeFromString(reader.nextString());
                switch (type) {
                    case GTPoint:
                        geometry = new GeoPoint();
                        break;
                    case GTLineString:
                        geometry = new GeoLineString();
                        break;
                    case GTPolygon:
                        geometry = new GeoPolygon();
                        break;
                    case GTMultiPoint:
                        geometry = new GeoMultiPoint();
                        break;
                    case GTMultiLineString:
                        geometry = new GeoMultiLineString();
                        break;
                    case GTMultiPolygon:
                        geometry = new GeoMultiPolygon();
                        break;
                    case GTGeometryCollection:
                        geometry = new GeoGeometryCollection();
                    case GTNone:
                    default:
                        break;
                }
            } else if(name.equalsIgnoreCase(GeoConstants.GEOJSON_COORDINATES)) {
                if (geometry == null)
                    reader.skipValue();
                else
                    geometry.setCoordinatesFromJSONStream(reader, crs);
            } else {
                reader.skipValue();
            }
        }
        reader.endObject();

        if (geometry != null) {
            geometry.setCRS(crs);
            if (!geometry.isValid())
                return null;
        }

        return geometry;
    }

    public static GeoGeometry fromJson(JSONObject jsonObject)
            throws JSONException
    {
        String jsonType = jsonObject.getString(GEOJSON_TYPE);
        int type = typeFromString(jsonType);

        GeoGeometry output = null;
        switch (type) {
            case GTPoint:
                output = new GeoPoint();
                break;
            case GTLineString:
                output = new GeoLineString();
                break;
            case GTPolygon:
                output = new GeoPolygon();
                break;
            case GTMultiPoint:
                output = new GeoMultiPoint();
                break;
            case GTMultiLineString:
                output = new GeoMultiLineString();
                break;
            case GTMultiPolygon:
                output = new GeoMultiPolygon();
                break;
            case GTGeometryCollection:
                output = new GeoGeometryCollection();
            case GTNone:
            default:
                break;
        }

        switch (type) {
            case GTPoint:
            case GTLineString:
            case GTPolygon:
            case GTMultiPoint:
            case GTMultiLineString:
            case GTMultiPolygon:
                JSONArray coordinates = jsonObject.getJSONArray(GEOJSON_COORDINATES);
                output.setCoordinatesFromJSON(coordinates);
                break;
            case GTGeometryCollection:
                JSONArray jsonGeometries = jsonObject.getJSONArray(GEOJSON_GEOMETRIES);
                output.setCoordinatesFromJSON(jsonGeometries);
                break;
            case GTNone:
            default:
                break;
        }

        return output;
    }


    public static int typeFromString(String jsonType)
    {
        switch (jsonType) {
            case "POINT":
            case GEOJSON_TYPE_Point:
                return GTPoint;

            case "LINESTRING":
            case GEOJSON_TYPE_LineString:
                return GTLineString;

            case "POLYGON":
            case GEOJSON_TYPE_Polygon:
                return GTPolygon;

            case "MULTIPOINT":
            case GEOJSON_TYPE_MultiPoint:
                return GTMultiPoint;

            case "MULTILINESTRING":
            case GEOJSON_TYPE_MultiLineString:
                return GTMultiLineString;

            case "MULTIPOLYGON":
            case GEOJSON_TYPE_MultiPolygon:
                return GTMultiPolygon;

            case "GEOMETRYCOLLECTION":
            case GEOJSON_TYPE_GeometryCollection:
                return GTGeometryCollection;

            default:
                return GTNone;
        }
    }

    public static String typeToString(int type) {
        switch (type) {
            case GTPoint:
                return "POINT";

            case GTLineString:
                return "LINESTRING";

            case GTPolygon:
                return "POLYGON";

            case GTMultiPoint:
                return "MULTIPOINT";

            case GTMultiLineString:
                return "MULTILINESTRING";

            case GTMultiPolygon:
                return "MULTIPOLYGON";

            case GTGeometryCollection:
                return "GEOMETRYCOLLECTION";

            default:
                return "";
        }
    }

    public static GeoGeometry fromBlobOld(byte[] raw)
            throws IOException, ClassNotFoundException
    {
        if (null == raw) {
            return null;
        }
        ByteArrayInputStream in = new ByteArrayInputStream(raw);
        ObjectInputStream is = new ObjectInputStream(in);
        return (GeoGeometry) is.readObject();
    }

    public static GeoGeometry fromBlob(byte[] raw)
            throws IOException
    {
        if (null == raw) {
            return null;
        }
        ByteArrayInputStream in = new ByteArrayInputStream(raw);
        DataInputStream dataInputStream = new DataInputStream(in);
        return fromDataStream(dataInputStream);
    }


    public static GeoGeometry fromWKT(String wkt, int crs)
    {
        GeoGeometry output = null;
        wkt = wkt.trim();
        if (wkt.startsWith("POINT")) {
            output = new GeoPoint();
            output.setCoordinatesFromWKT(wkt.substring(5).trim(), crs);
        } else if (wkt.startsWith("LINESTRING")) {
            output = new GeoLineString();
            output.setCoordinatesFromWKT(wkt.substring(10).trim(), crs);
        } else if (wkt.startsWith("LINEARRING")) {
            output = new GeoLinearRing();
            output.setCoordinatesFromWKT(wkt.substring(10).trim(), crs);
        } else if (wkt.startsWith("POLYGON")) {
            output = new GeoPolygon();
            output.setCoordinatesFromWKT(wkt.substring(8, wkt.length() - 1).trim(), crs);
        } else if (wkt.startsWith("MULTIPOINT")) {
            output = new GeoMultiPoint();
            output.setCoordinatesFromWKT(wkt.substring(10).trim(), crs);
        } else if (wkt.startsWith("MULTILINESTRING")) {
            output = new GeoMultiLineString();
            output.setCoordinatesFromWKT(wkt.substring(15).trim(), crs);
        } else if (wkt.startsWith("MULTIPOLYGON")) {
            output = new GeoMultiPolygon();
            output.setCoordinatesFromWKT(wkt.substring(12).trim(), crs);
        } else if (wkt.startsWith("GEOMETRYCOLLECTION")) {
            output = new GeoGeometryCollection();
            output.setCoordinatesFromWKT(wkt.substring(18).trim(), crs);
        }

        if (output != null)
            output.setCRS(crs);

        return output;
    }

    public static GeoGeometry fromDataStream(DataInputStream stream) throws IOException {
        int geometryType = stream.readInt();
        GeoGeometry result = null;
        switch (geometryType){
            case GeoConstants.GTPoint:
                result = new GeoPoint();
                result.read(stream);
                break;
            case GeoConstants.GTLineString:
                result = new GeoLineString();
                result.read(stream);
                break;
            case GeoConstants.GTLinearRing:
                result = new GeoLinearRing();
                result.read(stream);
                break;
            case GeoConstants.GTPolygon:
                result = new GeoPolygon();
                result.read(stream);
                break;
            case GeoConstants.GTMultiPoint:
                result = new GeoMultiPoint();
                result.read(stream);
                break;
            case GeoConstants.GTMultiLineString:
                result = new GeoMultiLineString();
                result.read(stream);
                break;
            case GeoConstants.GTMultiPolygon:
                result = new GeoMultiPolygon();
                result.read(stream);
                break;
            case GeoConstants.GTGeometryCollection:
                result = new GeoGeometryCollection();
                result.read(stream);
                break;
        }
        return result;
    }
} public class Connection
        implements INGWResource
{
    protected int mNgwVersionMajor = Constants.NOT_FOUND;
    protected int mNgwVersionMinor = Constants.NOT_FOUND;

    protected String        mName;
    protected String        mLogin;
    protected String        mPassword;
    protected String        mURL;
    protected boolean       mIsConnected;
    protected String        mCookie;
    protected List<Integer> mSupportedTypes;
    protected ResourceGroup mRootResource;
    protected int           mId;
    protected INGWResource  mParent;

    public final static int NGWResourceTypeNone              = 1 << 0;
    public final static int NGWResourceTypeResourceGroup     = 1 << 1;
    public final static int NGWResourceTypePostgisLayer      = 1 << 2;
    public final static int NGWResourceTypePostgisConnection = 1 << 3;
    public final static int NGWResourceTypeWMSServerService  = 1 << 4;
    public final static int NGWResourceTypeBaseLayers        = 1 << 5;
    public final static int NGWResourceTypeWebMap            = 1 << 6;
    public final static int NGWResourceTypeWFSServerService  = 1 << 7;
    public final static int NGWResourceTypeVectorLayer       = 1 << 8;
    public final static int NGWResourceTypeRasterLayer       = 1 << 9;
    public final static int NGWResourceTypeVectorLayerStyle  = 1 << 10;
    public final static int NGWResourceTypeRasterLayerStyle  = 1 << 11;
    public final static int NGWResourceTypeFileSet           = 1 << 12;
    public final static int NGWResourceTypeConnection        = 1 << 13;
    public final static int NGWResourceTypeConnections       = 1 << 14;
    public final static int NGWResourceTypeWMSClient         = 1 << 15;
    public final static int NGWResourceTypeLookupTable       = 1 << 16;


    public Connection(
            String name,
            String login,
            String password,
            String url)
    {
        mName = name;
        mLogin = login;
        mPassword = password;
        mURL = NGWUtil.getServerUrl(url);
        mIsConnected = false;
        mId = Connections.getNewId();
        mSupportedTypes = new ArrayList<>();
    }

    public boolean connect(boolean guest) {
        setNgwVersion();
        fillCapabilities();

        mRootResource = new ResourceGroup(0, this);
        mRootResource.setParent(this);

        if (!guest) {
            try {
                AtomicReference<String> reference = new AtomicReference<>(mURL);
                mCookie = NGWUtil.getConnectionCookie(reference, mLogin, mPassword);
                if (null == mCookie) {
                    return false;
                }
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }

        mIsConnected = true;
        return true;
    }

    protected void setNgwVersion()
    {
        Pair<Integer, Integer> ver = null;
        try {
            ver = NGWUtil.getNgwVersion(mURL, mLogin, mPassword);
        } catch (IOException | JSONException | NumberFormatException ignored) { }

        if (null != ver) {
            mNgwVersionMajor = ver.first;
            mNgwVersionMinor = ver.second;
        }
    }


    public int getNgwVersionMajor()
    {
        return mNgwVersionMajor;
    }


    public int getNgwVersionMinor()
    {
        return mNgwVersionMinor;
    }


    public String getCookie()
    {
        return mCookie;
    }


    public String getURL()
    {
        return mURL;
    }


    protected void fillCapabilities()
    {
        mSupportedTypes.clear();
        try {
            String sURL = mURL + "/resource/schema";
            HttpResponse response = NetworkUtil.get(sURL, getLogin(), getPassword(), false);
            if (!response.isOk())
                return;
            JSONObject schema = new JSONObject(response.getResponseBody());
            JSONObject resources = schema.getJSONObject("resources");
            if (null != resources) {
                Iterator<String> keys = resources.keys();
                while (keys.hasNext()) {
                    int type = getType(keys.next());
                    if (type != NGWResourceTypeNone) {
                        if (mSupportedTypes.isEmpty()) {
                            mSupportedTypes.add(type);
                        } else if (!isTypeSupported(type)) {
                            mSupportedTypes.add(type);
                        }
                    }
                }
            }
        } catch (IOException | JSONException e) {
            e.printStackTrace();
        }
    }


    public boolean isTypeSupported(int type)
    {
        return mSupportedTypes.isEmpty() || mSupportedTypes.contains(type);
    }


    public int getType(String sType)
    {
        switch (sType) {
            case "resource_group":
                return NGWResourceTypeResourceGroup;
            case "postgis_layer":
                return NGWResourceTypePostgisLayer;
            case "wmsserver_service":
                return NGWResourceTypeWMSServerService;
            case "baselayers":
                return NGWResourceTypeBaseLayers;
            case "postgis_connection":
                return NGWResourceTypePostgisConnection;
            case "webmap":
                return NGWResourceTypeWebMap;
            case "wfsserver_service":
                return NGWResourceTypeWFSServerService;
            case "vector_layer":
                return NGWResourceTypeVectorLayer;
            case "raster_layer":
                return NGWResourceTypeRasterLayer;
            case "file_bucket":
                return NGWResourceTypeFileSet;
            case "wmsclient_layer":
                return NGWResourceTypeWMSClient;
            case "lookup_table":
                return NGWResourceTypeLookupTable;
            default:
                return NGWResourceTypeNone;
        }
    }


    public ResourceGroup getRootResource()
    {
        return mRootResource;
    }


    @Override
    public String getName()
    {
        return mName;
    }


    @Override
    public int getType()
    {
        return NGWResourceTypeConnection;
    }


    @Override
    public int getId()
    {
        return mId;
    }


    @Override
    public INGWResource getResourceById(int id)
    {
        if (id == mId) {
            return this;
        }
        if (null == mRootResource) //not connected
        {
            return null;
        }
        return mRootResource.getResourceById(id);
    }


    @Override
    public int getChildrenCount()
    {
        if (null == mRootResource) {
            return 0;
        }
        return mRootResource.getChildrenCount();
    }


    @Override
    public INGWResource getChild(int i)
    {
        if (null == mRootResource) {
            return null;
        }
        return mRootResource.getChild(i);
    }


    @Override
    public INGWResource getParent()
    {
        return mParent;
    }


    @Override
    public void setParent(INGWResource resource)
    {
        mParent = resource;
    }

    @Override
    public String getKey() {
        return "";
    }


    public void loadChildren()
    {
        if (null != mRootResource) {
            mRootResource.loadChildren();
        }
    }


    @Override
    public int describeContents()
    {
        return 0;
    }


    @Override
    public void writeToParcel(
            Parcel parcel,
            int i)
    {
        parcel.writeString(mName);
        parcel.writeString(mLogin);
        parcel.writeString(mPassword);
        parcel.writeString(mURL);
        parcel.writeByte(mIsConnected ? (byte) 1 : (byte) 0);
        parcel.writeString(mCookie);
        parcel.writeInt(mId);
        parcel.writeInt(mSupportedTypes.size());
        for (Integer type : mSupportedTypes) {
            parcel.writeInt(type);
        }
        parcel.writeParcelable(mRootResource, i);
    }


    public static final Parcelable.Creator<Connection> CREATOR =
            new Parcelable.Creator<Connection>()
            {
                public Connection createFromParcel(Parcel in)
                {
                    return new Connection(in);
                }


                public Connection[] newArray(int size)
                {
                    return new Connection[size];
                }
            };


    protected Connection(Parcel in)
    {
        mName = in.readString();
        mLogin = in.readString();
        mPassword = in.readString();
        mURL = in.readString();
        mIsConnected = in.readByte() == 1;
        mCookie = in.readString();
        mId = in.readInt();
        int count = in.readInt();
        mSupportedTypes = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            mSupportedTypes.add(in.readInt());
        }
        mRootResource = in.readParcelable(ResourceGroup.class.getClassLoader());
        if (null != mRootResource) {
            mRootResource.setConnection(this);
            mRootResource.setParent(this);
        }
    }


    public boolean isConnected()
    {
        return mIsConnected;
    }


    public String getLogin()
    {
        return mLogin;
    }


    public String getPassword()
    {
        return mPassword;
    }
} public class SyncAdapter
        extends AbstractThreadedSyncAdapter
{
    public static final String SYNC_START    = "com.nextgis.maplib.sync_start";
    public static final String SYNC_FINISH   = "com.nextgis.maplib.sync_finish";
    public static final String SYNC_CANCELED = "com.nextgis.maplib.sync_canceled";
    public static final String SYNC_CHANGES  = "com.nextgis.maplib.sync_changes";

    public static final String EXCEPTION = "exception";
    protected String mError;

    private HashMap<String, Pair<Integer, Integer>> mVersions;

    public SyncAdapter(
            Context context,
            boolean autoInitialize)
    {
        super(context, autoInitialize);
    }


    public SyncAdapter(
            Context context,
            boolean autoInitialize,
            boolean allowParallelSyncs)
    {
        super(context, autoInitialize, allowParallelSyncs);
    }


    /**
     * Warning! When you stop the sync service by ContentResolver.cancelSync() then onPerformSync
     * stops after end of syncing of current NGWVectorLayer. The data structure of the current
     * NGWVectorLayer will be saved.
     * <p/>
     * <b>Description copied from class:</b> AbstractThreadedSyncAdapter Perform a sync for this
     * account. SyncAdapter-specific parameters may be specified in extras, which is guaranteed to
     * not be null. Invocations of this method are guaranteed to be serialized.
     */
    @Override
    public void onPerformSync(
            Account account,
            Bundle bundle,
            String authority,
            ContentProviderClient contentProviderClient,
            SyncResult syncResult)
    {
        Log.d(TAG, "onPerformSync");
        HyperLog.v(Constants.TAG, "SyncAdapter: onPerformSync");

        MapContentProviderHelper mapContentProviderHelper =(MapContentProviderHelper) MapBase.getInstance();
        getContext().sendBroadcast(new Intent(SYNC_START));

        mVersions = new HashMap<>();
        HyperLog.v(Constants.TAG, "SyncAdapter: mapContentProviderHelper is " + mapContentProviderHelper);
        if (null != mapContentProviderHelper) {
            // FIXME Temporary fix till 3.0
//            mapContentProviderHelper.load(); // reload map for deleted/added layers
            sync(mapContentProviderHelper, authority, syncResult);
        }

        if (isCanceled()) {
            Log.d(Constants.TAG, "onPerformSync - SYNC_CANCELED is sent");
            HyperLog.v(Constants.TAG, "SyncAdapter: SYNC_CANCELED is sent");
            getContext().sendBroadcast(new Intent(SYNC_CANCELED));
            return;
        }

        final String accountNameHash = "_" + account.name.hashCode();
        SharedPreferences settings = getContext().getSharedPreferences(Constants.PREFERENCES, MODE_MULTI_PROCESS);
        SharedPreferences.Editor editor = settings.edit();
        editor.putLong(SettingsConstants.KEY_PREF_LAST_SYNC_TIMESTAMP + accountNameHash, System.currentTimeMillis());
        editor.putLong(SettingsConstants.KEY_PREF_LAST_SYNC_TIMESTAMP, System.currentTimeMillis());
        editor.apply();

        mError = "";
        if (syncResult.stats.numIoExceptions > 0)
            mError += getContext().getString(R.string.sync_error_io);
        if (syncResult.stats.numParseExceptions > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_parse);
        }
        if (syncResult.stats.numAuthExceptions > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.error_auth);
        }
        if (syncResult.stats.numConflictDetectedExceptions > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_conflict);
        }
        if (syncResult.stats.numInserts > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_insert);
        }
        if (syncResult.stats.numUpdates > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_change);
        }
        if (syncResult.stats.numDeletes > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_delete);
        }
        if (syncResult.stats.numEntries > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_server);
        }
        if (syncResult.stats.numSkippedEntries > 0) {
            if (mError.length() > 0)
                mError += "\r\n";
            mError += getContext().getString(R.string.sync_error_oom);
        }

        Intent finish = new Intent(SYNC_FINISH);
        if (!TextUtils.isEmpty(mError))
            finish.putExtra(EXCEPTION, mError);
        HyperLog.v(Constants.TAG, "SyncAdapter: SYNC_FINISH is sent / mError is " + mError);
        getContext().sendBroadcast(finish);
    }


    protected void sync(
            LayerGroup layerGroup,
            String authority,
            SyncResult syncResult)
    {
        HyperLog.v(Constants.TAG, "SyncAdapter: total layers for sync in " + layerGroup + " is " + layerGroup.getLayerCount());
        for (int i = 0; i < layerGroup.getLayerCount(); i++) {
            if (isCanceled()) {
                return;
            }
            ILayer layer = layerGroup.getLayer(i);
            if (layer instanceof LayerGroup) {
                HyperLog.v(Constants.TAG, "SyncAdapter: " + layer + " is a layer group");
                sync((LayerGroup) layer, authority, syncResult);
            } else if (layer instanceof INGWLayer) {
                HyperLog.v(Constants.TAG, "SyncAdapter: " + layer + " is a NGW layer");
                INGWLayer ngwLayer = (INGWLayer) layer;
                String accountName = ngwLayer.getAccountName();
                if (!mVersions.containsKey(accountName))
                    mVersions.put(accountName, NGWUtil.getNgwVersion(getContext(), accountName));

                Pair<Integer, Integer> ver = mVersions.get(accountName);
                ngwLayer.sync(authority, ver, syncResult);
            } else if (layer instanceof TrackLayer) {
                HyperLog.v(Constants.TAG, "SyncAdapter: " + layer + " is a tracking layer");
                ((TrackLayer) layer).sync();
            }
        }
    }

    @SuppressLint("MissingPermission")
    public static void setSyncPeriod(
            IGISApplication application,
            Bundle extras,
            long pollFrequency)
    {
        Context context = ((Context) application).getApplicationContext();
        final AccountManager accountManager = AccountManager.get(context);
        Log.d(TAG, "SyncAdapter: AccountManager.get(" + context + ")");

        for (Account account : accountManager.getAccountsByType(application.getAccountsType())) {
            ContentResolver.addPeriodicSync(account, application.getAuthority(), extras, pollFrequency);
        }
    }


    public boolean isCanceled()
    {
        return Thread.currentThread().isInterrupted();
    }
} public class AccountUtil {
    public static boolean isProUser(Context context) {
        File support = context.getExternalFilesDir(null);
        if (support == null)
            support = new File(context.getFilesDir(), SUPPORT);
        else
            support = new File(support, SUPPORT);

        try {
            String jsonString = FileUtil.readFromFile(support);
            JSONObject json = new JSONObject(jsonString);
            if (json.optBoolean(JSON_SUPPORTED_KEY)) {
                final String id = json.getString(JSON_USER_ID_KEY);
                final String start = json.getString(JSON_START_DATE_KEY);
                final String end = json.getString(JSON_END_DATE_KEY);
                final String data = id + start + end + "true";
                final String signature = json.getString(JSON_SIGNATURE_KEY);

                return verifySignature(data, signature);
            }
        } catch (JSONException | IOException ignored) { }

        return false;
    }

    private static boolean verifySignature(String data, String signature) {
        try {
            // add public key
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            String key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzbmnrTLjTLxqCnIqXgIJ\n" +
                    "jebXVOn4oV++8z5VsBkQwK+svDkGK/UcJ4YjXUuPqyiZwauHGy1wizGCgVIRcPNM\n" +
                    "I0n9W6797NMFaC1G6Rp04ISv7DAu0GIZ75uDxE/HHDAH48V4PqQeXMp01Uf4ttti\n" +
                    "XfErPKGio7+SL3GloEqtqGbGDj6Yx4DQwWyIi6VvmMsbXKmdMm4ErczWFDFHIxpV\n" +
                    "ln/VfX43r/YOFxqt26M7eTpaBIvAU6/yWkIsvidMNL/FekQVTiRCl/exPgioDGrf\n" +
                    "06z5a0sd3NDbS++GMCJstcKxkzk5KLQljAJ85Jciiuy2vv14WU621ves8S9cMISO\n" + "HwIDAQAB";
            byte[] keyBytes = Base64.decode(key.getBytes("UTF-8"), Base64.DEFAULT);
            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            PublicKey publicKey = keyFactory.generatePublic(spec);

            // verify signature
            Signature signCheck = Signature.getInstance("SHA256withRSA");
            signCheck.initVerify(publicKey);
            signCheck.update(data.getBytes("UTF-8"));
            byte[] sigBytes = Base64.decode(signature, Base64.DEFAULT);
            return signCheck.verify(sigBytes);
        } catch (Exception e) {
            return false;
        }
    }

    public static boolean isSyncActive(Account account, String authority) {
        return isSyncActiveHoneycomb(account, authority);
    }

    public static boolean isSyncActiveHoneycomb(Account account, String authority) {
        for (SyncInfo syncInfo : ContentResolver.getCurrentSyncs()) {
            if (syncInfo.account.equals(account) && syncInfo.authority.equals(authority)) {
                return true;
            }
        }
        return false;
    }

    public static AccountData getAccountData(Context context, String accountName) throws IllegalStateException {
        IGISApplication app = (IGISApplication) context.getApplicationContext();
        Account account = app.getAccount(accountName);

        if (null == account) {
            throw new IllegalStateException("Account is null");
        }

        AccountData accountData = new AccountData();

        accountData.url = app.getAccountUrl(account);
        accountData.login = app.getAccountLogin(account);
        accountData.password = app.getAccountPassword(account);

        return accountData;
    }

    public static class AccountData {
        public String url;
        public String login;
        public String password;
    }
} public class AttachItem implements IJSONStore {
    String mDescription;
    String mDisplayName;
    String mMimetype;
    String mAttachId;
    int mSize;

    protected static final String JSON_ID_KEY = "id";
    protected static final String JSON_DESCRIPTION_KEY = "desc";
    protected static final String JSON_MIME_KEY = "mime";
    protected static final String JSON_DISPLAY_NAME_KEY = "display_name";
    protected static final String JSON_SIZE_KEY = Constants.JSON_SIZE_KEY;

    public AttachItem() {
    }

    public AttachItem(String attachId, String displayName, String mimetype, String description) {
        mDescription = description;
        mDisplayName = displayName;
        mMimetype = mimetype;
        mAttachId = attachId;
    }

    public AttachItem(String attachId, String displayName, String mimetype, String description, int size) {
        mDescription = description;
        mDisplayName = displayName;
        mMimetype = mimetype;
        mAttachId = attachId;
        mSize = size;
    }

    @Override
    public JSONObject toJSON() throws JSONException {
        JSONObject out = new JSONObject();
        out.put(JSON_ID_KEY, mAttachId);
        out.put(JSON_DESCRIPTION_KEY, mDescription);
        out.put(JSON_MIME_KEY, mMimetype);
        out.put(JSON_DISPLAY_NAME_KEY, mDisplayName);
        out.put(JSON_SIZE_KEY, mSize);
        return out;
    }

    @Override
    public void fromJSON(JSONObject jsonObject) throws JSONException {
        mAttachId = jsonObject.getString(JSON_ID_KEY);
        mDisplayName = jsonObject.getString(JSON_DISPLAY_NAME_KEY);
        mMimetype = jsonObject.getString(JSON_MIME_KEY);
        mDescription = jsonObject.getString(JSON_DESCRIPTION_KEY);
        mSize = jsonObject.optInt(JSON_SIZE_KEY);
    }

    public String getDescription() {
        return mDescription;
    }

    public String getDisplayName() {
        return mDisplayName;
    }

    public String getMimetype() {
        return mMimetype;
    }

    public String getAttachId() {
        return mAttachId;
    }

    public int getSize() {
        return mSize;
    }

    public void setDescription(String description) {
        mDescription = description;
    }

    public void setDisplayName(String displayName) {
        mDisplayName = displayName;
    }

    public void setMimetype(String mimetype) {
        mMimetype = mimetype;
    }

    public void setAttachId(String attachId) {
        mAttachId = attachId;
    }

    public void setSize(int size) {
        mSize = size;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        AttachItem that = (AttachItem) o;

        return that.mSize == mSize && mAttachId.equals(that.mAttachId) && !(mDescription != null ? !mDescription
                .equals(that.mDescription) : that.mDescription != null) && !(mDisplayName != null ? !mDisplayName
                .equals(that.mDisplayName) : that.mDisplayName != null) && !(mMimetype != null ? !mMimetype.equals(that.mMimetype) : that.mMimetype != null);

    }

    @Override
    public int hashCode() {
        return mAttachId.hashCode();
    }

    public ContentValues getContentValues(boolean withAttachId) {
        ContentValues returnValues = new ContentValues();
        returnValues.put(VectorLayer.ATTACH_DISPLAY_NAME, mDisplayName);
        returnValues.put(VectorLayer.ATTACH_MIME_TYPE, mMimetype);
        returnValues.put(VectorLayer.ATTACH_DESCRIPTION, mDescription);
        returnValues.put(VectorLayer.ATTACH_SIZE, mSize);

        if (withAttachId) {
            returnValues.put(VectorLayer.ATTACH_ID, mAttachId);
        }

        return returnValues;
    }
} public interface Constants
{
    String  TAG                = "nextgismobile";
    String  PREFERENCES        = "nextgismobile";
    String  SUPPORT            = "support.bin";
    int     NOT_FOUND          = -1;
    boolean DEBUG_MODE         = false;

    int NGW_v3 = 3;

    /**
     * HTTP parameters
     */
    String APP_USER_AGENT     = "NextGIS Mobile maplib v" + BuildConfig.VERSION_NAME;
    int    IO_BUFFER_SIZE     = 32 * 1024; // 32k
    int    MAX_CONTENT_LENGTH = 5 * 1024 * 1024; //5Mb

    long MIN_LOCAL_FEATURE_ID = 10000000;
    int  MAX_TILES_COUNT      = 6001;

    /**
     * NGW account type
     */
    String NGW_ACCOUNT_TYPE = "com.nextgis.account";
    String NGW_ACCOUNT_GUEST = "anonymous";

    /**
     * Map parameters
     */
    float MIN_SCROLL_STEP = 5.5f;
    int   MAP_LIMITS_NO   = 1; // no limits to scroll map
    int   MAP_LIMITS_X    = 2; // limit to scroll map by x axis
    int   MAP_LIMITS_Y    = 3; // limit to scroll map by y axis
    int   MAP_LIMITS_XY   = 4; // limit to scroll map by x & y axis

    /**
     * The additional size to off screen drawing from 1 and higher As more than more memory needed
     */

    float OFFSCREEN_EXTRASIZE_RATIO = 1.0f;
    int   DEFAULT_TILE_SIZE         = 256;

    /**
     * thread priorities and delays
     */
    int DEFAULT_DRAW_THREAD_PRIORITY       = android.os.Process.THREAD_PRIORITY_DEFAULT + 11;
    int DEFAULT_DOWNLOAD_THREAD_PRIORITY   = android.os.Process.THREAD_PRIORITY_BACKGROUND + 3;
    int DEFAULT_LOAD_LAYER_THREAD_PRIORITY = Thread.MIN_PRIORITY;
    int DEFAULT_EXECUTION_DELAY            = 650;

    /**
     * tune line string and linear ring simplifier
     */
    double SIMPLIFY_TOENV_AREA_MULTIPLY = 1.5;
    // area multiplier to skip if greater than quad tolerance
    double SIMPLIFY_SKIP_AREA_MULTIPLY  = 5;
    int    SAMPLE_DISTANCE_PX           = 5;


    String CONFIG       = "config.json";
    String LAYER_PREFIX = "layer_";
    String MAP_EXT      = ".ngm";

    /**
     * notifications
     */
    String NOTIFY_DELETE            = "com.nextgis.maplib.notify_delete";
    String NOTIFY_DELETE_ALL        = "com.nextgis.maplib.notify_delete_all";
    String NOTIFY_INSERT            = "com.nextgis.maplib.notify_insert";
    String NOTIFY_UPDATE            = "com.nextgis.maplib.notify_update";
    String NOTIFY_UPDATE_ALL        = "com.nextgis.maplib.notify_update_all";
    String NOTIFY_UPDATE_FIELDS     = "com.nextgis.maplib.notify_update_fields";
    String NOTIFY_FEATURE_ID_CHANGE = "com.nextgis.maplib.notify_change_id";

    String NOTIFY_LAYER_NAME = "layer_name";

    /**
     * JSON keys
     */
    String JSON_ID_KEY            = "id";
    String JSON_NAME_KEY          = "name";
    String JSON_VISIBILITY_KEY    = "visible";
    String JSON_LEVELS_KEY        = "levels";
    String JSON_LEVEL_KEY         = "level";
    String JSON_TYPE_KEY          = "type";
    String JSON_MAXLEVEL_KEY      = "max_level";
    String JSON_MINLEVEL_KEY      = "min_level";
    String JSON_LAYERS_KEY        = "layers";
    String JSON_LAYER_KEY         = "layer";
    String JSON_PATH_KEY          = "path";
    String JSON_BBOX_MINX_KEY     = "bbox_minx";
    String JSON_BBOX_MINY_KEY     = "bbox_miny";
    String JSON_BBOX_MAXX_KEY     = "bbox_maxx";
    String JSON_BBOX_MAXY_KEY     = "bbox_maxy";
    String JSON_RENDERERPROPS_KEY = "renderer_properties";
    String JSON_STYLE_RULE_KEY    = "style_rule";
    String JSON_WIDTH_KEY         = "width";
    String JSON_COLOR_KEY         = "color";
    String JSON_OUTCOLOR_KEY      = "out_color";
    String JSON_ALPHA_KEY         = "alpha";
    String JSON_OUTALPHA_KEY      = "out_alpha";
    String JSON_CHANGES_KEY       = "changes";
    String JSON_VALUE_KEY         = "value";
    String JSON_SIZE_KEY          = "size";
    String JSON_TEXT_SIZE_KEY     = "text_size";
    String JSON_TEXT_ALIGN_KEY    = "text_alignment";
    String JSON_TEXT_COLOR_KEY    = "text_color";
    String JSON_DISPLAY_NAME      = "display_name";
    String JSON_RESOURCE_KEY      = "resource";
    String JSON_MESSAGE_KEY       = "message";
    String JSON_EXTENT_KEY        = "extent";
    String JSON_MIN_LAT_KEY       = "minLat";
    String JSON_MAX_LAT_KEY       = "maxLat";
    String JSON_MIN_LON_KEY       = "minLon";
    String JSON_MAX_LON_KEY       = "maxLon";
    String JSON_SUPPORTED_KEY     = "supported";
    String JSON_START_DATE_KEY    = "start_date";
    String JSON_END_DATE_KEY      = "end_date";
    String JSON_USER_ID_KEY       = "nextgis_guid";
    String JSON_SIGNATURE_KEY     = "sign";

    /**
     * database fields
     */
    String FIELD_ID               = "_id";
    String FIELD_OLD_ID           = "old_id";
    String FIELD_GEOM             = "_geom";
    String FIELD_GEOM_            = "_geom_";
    String FIELD_FEATURE_ID       = "feature_id";
    String FIELD_OPERATION        = "operation";
    String FIELD_ATTACH_ID        = "attach_id";
    String FIELD_ATTACH_OPERATION = "attach_operation";

    String ATTRIBUTES_ONLY = "attributes_only";

    /**
     * Layer type
     */
    String CE50 = "CE50";
    String CE90 = "CE90";
    String CE95 = "CE95";
    String CE98 = "CE98";

    /**
     * Layer type
     */
    int LAYERTYPE_REMOTE_TMS   = 1 << 0;
    int LAYERTYPE_NGW_RASTER   = 1 << 1;
    int LAYERTYPE_NGW_VECTOR   = 1 << 2;
    int LAYERTYPE_GROUP        = 1 << 3;
    int LAYERTYPE_LOCAL_VECTOR = 1 << 4;
    int LAYERTYPE_LOCAL_TMS    = 1 << 5;
    int LAYERTYPE_TRACKS       = 1 << 6;
    int LAYERTYPE_LOOKUPTABLE  = 1 << 7;
    int LAYERTYPE_NGW_WEBMAP   = 1 << 8;

    int LAYERTYPE_SYSMAX = 9; // should be the max + 1 of system layer type

    /**
     * File type
     */
    int FILETYPE_PARENT  = 1 << 0;
    int FILETYPE_FOLDER  = 1 << 1;
    int FILETYPE_ZIP     = 1 << 2;
    int FILETYPE_GEOJSON = 1 << 3;
    int FILETYPE_FB      = 1 << 4;
    int FILETYPE_UNKNOWN = 1 << 31;

    /**
     * time constants
     */
    long     ONE_SECOND           = 1000;
    long     ONE_MINUTE           = ONE_SECOND * 60;
    long     ONE_HOUR             = ONE_MINUTE * 60;
    long     ONE_DAY              = ONE_HOUR * 24;
    long     ONE_WEEK             = ONE_DAY * 7;
    long     DEFAULT_TILE_MAX_AGE = ONE_WEEK;
    long     ONE_YEAR             = ONE_DAY * 365;
    int      KEEP_ALIVE_TIME      = 35;
    int      TERMINATE_TIME       = 350;
    TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.MILLISECONDS;

    int  SYNC_NONE           = 1 << 0;
    int  SYNC_GEOMETRY       = 1 << 1;
    int  SYNC_ATTRIBUTES     = 1 << 2;
    int  SYNC_DATA           = SYNC_GEOMETRY | SYNC_ATTRIBUTES;
    int  SYNC_ATTACH         = 1 << 3;
    int  SYNC_ALL            = SYNC_DATA | SYNC_ATTACH;
    long DEFAULT_SYNC_PERIOD = 3600; //1 hour

    String URI_ATTACH  = "attach";
    String URI_CHANGES = "changes";

    // http://stackoverflow.com/a/24055457
    String URI_PARAMETER_LIMIT    = "limit";
    String URI_PARAMETER_TEMP     = "temp";
    String URI_PARAMETER_NOT_SYNC = "not_sync";

    String CHANGES_NAME_POSTFIX      = "_changes";
    int    CHANGE_OPERATION_TEMP     = 1;
    int    CHANGE_OPERATION_NEW      = 1 << 1; // 2
    int    CHANGE_OPERATION_CHANGED  = 1 << 2; // 4
    int    CHANGE_OPERATION_DELETE   = 1 << 3; // 8
    int    CHANGE_OPERATION_ATTACH   = 1 << 4; // 16
    int    CHANGE_OPERATION_NOT_SYNC = 1 << 5; // 32

    int DRAWING_SEPARATE_THREADS = 9;
    int DRAW_NOTIFY_STEP_PERCENT = 10;
    int DRAW_FINISH_ID = -22;

    String[] VECTOR_FORBIDDEN_FIELDS = {
            "ABORT",
            "ACTION",
            "ADD",
            "AFTER",
            "ALL",
            "ALTER",
            "ANALYZE",
            "AND",
            "AS",
            "ASC",
            "ATTACH",
            "AUTOINCREMENT",
            "BEFORE",
            "BEGIN",
            "BETWEEN",
            "BY",
            "CASCADE",
            "CASE",
            "CAST",
            "CHECK",
            "COLLATE",
            "COLUMN",
            "COMMIT",
            "CONFLICT",
            "CONSTRAINT",
            "CREATE",
            "CROSS",
            "CURRENT_DATE",
            "CURRENT_TIME",
            "CURRENT_TIMESTAMP",
            "DATABASE",
            "DEFAULT",
            "DEFERRABLE",
            "DEFERRED",
            "DELETE",
            "DESC",
            "DETACH",
            "DISTINCT",
            "DROP",
            "EACH",
            "ELSE",
            "END",
            "ESCAPE",
            "EXCEPT",
            "EXCLUSIVE",
            "EXISTS",
            "EXPLAIN",
            "FAIL",
            "FOR",
            "FOREIGN",
            "FROM",
            "FULL",
            "GLOB",
            "GROUP",
            "HAVING",
            "IF",
            "IGNORE",
            "IMMEDIATE",
            "IN",
            "INDEX",
            "INDEXED",
            "INITIALLY",
            "INNER",
            "INSERT",
            "INSTEAD",
            "INTERSECT",
            "INTO",
            "IS",
            "ISNULL",
            "JOIN",
            "KEY",
            "LEFT",
            "LIKE",
            "LIMIT",
            "MATCH",
            "NATURAL",
            "NO",
            "NOT",
            "NOTNULL",
            "NULL",
            "OF",
            "OFFSET",
            "ON",
            "OR",
            "ORDER",
            "OUTER",
            "PLAN",
            "PRAGMA",
            "PRIMARY",
            "QUERY",
            "RAISE",
            "RECURSIVE",
            "REFERENCES",
            "REGEXP",
            "REINDEX",
            "RELEASE",
            "RENAME",
            "REPLACE",
            "RESTRICT",
            "RIGHT",
            "ROLLBACK",
            "ROW",
            "SAVEPOINT",
            "SELECT",
            "SET",
            "TABLE",
            "TEMP",
            "TEMPORARY",
            "THEN",
            "TO",
            "TRANSACTION",
            "TRIGGER",
            "UNION",
            "UNIQUE",
            "UPDATE",
            "USING",
            "VACUUM",
            "VALUES",
            "VIEW",
            "VIRTUAL",
            "WHEN",
            "WHERE",
            "WITH",
            "WITHOUT"};

} public class DatabaseContext
        extends ContextWrapper
{
    protected File mDatabasePath;


    public DatabaseContext(
            Context base,
            File databasePath)
    {
        super(base);
        mDatabasePath = databasePath;
    }


    @Override
    public File getDatabasePath(String name)
    {
        String dbfile = mDatabasePath + File.separator + name;
        if (!dbfile.endsWith(".db")) {
            dbfile += ".db";
        }

        if (!mDatabasePath.exists()) {
            mDatabasePath.mkdirs();
        }

        return new File(dbfile);
    }


    @Override
    public SQLiteDatabase openOrCreateDatabase(
            String name,
            int mode,
            SQLiteDatabase.CursorFactory factory,
            DatabaseErrorHandler errorHandler)
    {
        return SQLiteDatabase.openOrCreateDatabase(
                getDatabasePath(name).getAbsolutePath(), factory, errorHandler);
    }


    @Override
    public SQLiteDatabase openOrCreateDatabase(
            String name,
            int mode,
            SQLiteDatabase.CursorFactory factory)
    {
        SQLiteDatabase result = null;
        try {
            result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), factory);
        } catch (SQLiteException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static SQLiteDatabase getDbForLayer(final VectorLayer layer){
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(false);
        // speedup writing
        db.rawQuery("PRAGMA synchronous=OFF", null);
        //db.rawQuery("PRAGMA locking_mode=EXCLUSIVE", null);
        db.rawQuery("PRAGMA journal_mode=OFF", null);
        db.rawQuery("PRAGMA count_changes=OFF", null);
        db.rawQuery("PRAGMA cache_size=15000", null);

        return db;
    }
} public class FeatureChanges
{
    public static void initialize(String tableName)
    {
        Log.d(TAG, "init the change log for the layer " + tableName);

        String sqlCreateTable = "CREATE TABLE IF NOT EXISTS " + tableName + " ( ";
        sqlCreateTable += FIELD_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, ";
        sqlCreateTable += FIELD_FEATURE_ID + " INTEGER, ";
        sqlCreateTable += FIELD_OPERATION + " INTEGER, ";
        sqlCreateTable += FIELD_ATTACH_ID + " INTEGER, ";
        sqlCreateTable += FIELD_ATTACH_OPERATION + " INTEGER";
        sqlCreateTable += " );";

        Log.d(TAG, "create the layer change table: " + sqlCreateTable);

        // create table
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(true);
        db.execSQL(sqlCreateTable);
    }


    public static Cursor query(
            String tableName,
            String[] projection,
            String selection,
            String[] selectionArgs,
            String sortOrder,
            String limit)

    {
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(true);

        try {
            return db.query(
                    tableName, projection, selection, selectionArgs, null, null, sortOrder, limit);
        } catch (SQLiteException e) {
            Log.d(TAG, e.getLocalizedMessage());
            return null;
        }
    }


    public static Cursor query(
            String tableName,
            String selection,
            String sortOrder,
            String limit)
    {
        return query(tableName, null, selection, null, sortOrder, limit);
    }


    public static long insert(
            String tableName,
            ContentValues values)
    {
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(false);
        return db.insert(tableName, null, values);
    }


    public static long replace(
            String tableName,
            ContentValues values)
    {
        long featureId = values.getAsLong(FIELD_FEATURE_ID);
        int featureOperation = values.getAsInteger(FIELD_OPERATION);
        long attachId = values.getAsLong(FIELD_ATTACH_ID);
        int attachOperation = values.getAsInteger(FIELD_ATTACH_OPERATION);

        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                FIELD_OPERATION + " = " + featureOperation + " AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                FIELD_ATTACH_OPERATION + " = " + attachOperation;

        Cursor cursor = query(tableName, selection, null, "1");
        long res = 0;

        if (null != cursor) {
            res = cursor.getCount();
            cursor.close();
        }

        if (res > 0) {
            return res;
        }

        return insert(tableName, values);
    }


    public static int update(
            String tableName,
            ContentValues values,
            String selection,
            String[] selectionArgs)
    {
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(true);
        return db.update(tableName, values, selection, selectionArgs);
    }


    public static int delete(
            String tableName,
            String selection,
            String[] selectionArgs)
    {
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(true);
        int retResult = 0;
        try {
            retResult = db.delete(tableName, selection, selectionArgs);
        } catch (SQLiteException e) {
            e.printStackTrace();
            Log.d(TAG, e.getLocalizedMessage());
        }
        return retResult;
    }


    public static int delete(
            String tableName,
            String selection)
    {
        return delete(tableName, selection, null);
    }


    public static void delete(String tableName)
    {
        try {
            MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
            SQLiteDatabase db = map.getDatabase(true);
            String tableDrop = "DROP TABLE IF EXISTS " + tableName;
            db.execSQL(tableDrop);
        } catch (SQLiteFullException | SQLiteReadOnlyDatabaseException e) {
            e.printStackTrace();
        }
    }


    public static boolean isRecords(
            String tableName,
            String selection)
    {
        Cursor cursor = query(tableName, selection, null, "1");
        boolean ret = false;

        if (null != cursor) {
            if (cursor.getCount() > 0) {
                ret = true;
            }
            cursor.close();
        }

        return ret;
    }


    public static long getEntriesCount(String tableName)
    {
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(true);

        try {
            return DatabaseUtils.queryNumEntries(db, tableName);
        } catch (SQLiteException e) {
            e.printStackTrace();
            Log.d(TAG, e.getLocalizedMessage());
            return 0;
        }
    }


    protected static String getSelectionForSync()
    {
        return "( " +

                "0 == " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " AND " +
                "0 == " + FIELD_OPERATION + " & " + CHANGE_OPERATION_TEMP +
                " AND " +
                "0 == " + FIELD_OPERATION + " & " + CHANGE_OPERATION_NOT_SYNC +

                " OR " +

                "0 != " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " AND " +
                "0 == " + FIELD_ATTACH_OPERATION + " & " + CHANGE_OPERATION_TEMP +
                " AND " +
                "0 == " + FIELD_ATTACH_OPERATION + " & " + CHANGE_OPERATION_NOT_SYNC +

                " )";
    }


    public static long getChangeCount(String tableName)
    {
        String selection = getSelectionForSync();
        MapContentProviderHelper map = (MapContentProviderHelper) MapBase.getInstance();
        SQLiteDatabase db = map.getDatabase(true);

        try {
            // From sources of DatabaseUtils.queryNumEntries()
            String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : "";
            return DatabaseUtils.longForQuery(db, "select count(*) from " + tableName + s, null);
        } catch (SQLiteException e) {
            e.printStackTrace();
            Log.d(TAG, e.getLocalizedMessage());
            return 0;
        }
    }


    public static Cursor getFirstChangeFromRecordId(
            String tableName,
            long recordId)
    {
        String sortOrder = FIELD_ID + " ASC";
        String selection = FIELD_ID + " >= " + recordId + " AND " + getSelectionForSync();
        return query(tableName, selection, sortOrder, "1");
    }


    public static long getLastChangeRecordId(String tableName)
    {
        String sortOrder = FIELD_ID + " DESC";
        String selection = getSelectionForSync();
        Cursor cursor = query(tableName, selection, sortOrder, "1");
        long ret = NOT_FOUND;

        if (null == cursor) {
            return ret;
        }

        try {
            if (cursor.moveToFirst()) {
                ret = cursor.getLong(cursor.getColumnIndex(FIELD_ID));
            }
        } catch (Exception e) {
            //Log.d(TAG, e.getLocalizedMessage());
        } finally {
            cursor.close();
        }

        return ret;
    }


    public static Cursor getChanges(String tableName)
    {
        String sortOrder = FIELD_ID + " ASC";
        String selection = getSelectionForSync();
        return query(tableName, selection, sortOrder, null);
    }


    public static boolean isChanges(String tableName)
    {
        String selection = getSelectionForSync();
        return isRecords(tableName, selection);
    }


    public static Cursor getChanges(
            String tableName,
            long featureId)
    {
        String sortOrder = FIELD_ID + " ASC";
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " + getSelectionForSync();
        return query(tableName, selection, sortOrder, null);
    }


    public static boolean isChanges(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " + getSelectionForSync();
        return isRecords(tableName, selection);
    }


    public static Cursor getChanges(
            String tableName,
            long featureId,
            int operation)
    {
        String sortOrder = FIELD_ID + " ASC";
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + operation + " ) )" + " AND "
                + getSelectionForSync();

        return query(tableName, selection, sortOrder, null);
    }


    public static boolean isChanges(
            String tableName,
            long featureId,
            int operation)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + operation + " ) )" + " AND "
                + getSelectionForSync();

        return isRecords(tableName, selection);
    }


    public static Cursor getAttachChanges(
            String tableName,
            long featureId)
    {
        String sortOrder = FIELD_ID + " ASC";
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH + " ) )" + " AND "
                + getSelectionForSync();

        return query(tableName, selection, sortOrder, null);
    }


    public static boolean isAttachChanges(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH + " ) )" + " AND "
                + getSelectionForSync();

        return isRecords(tableName, selection);
    }


    public static Cursor getAttachChanges(
            String tableName,
            long featureId,
            long attachId)
    {
        String sortOrder = FIELD_ID + " ASC";
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                getSelectionForSync();

        return query(tableName, selection, sortOrder, null);
    }


    public static boolean isAttachChanges(
            String tableName,
            long featureId,
            long attachId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                getSelectionForSync();

        return isRecords(tableName, selection);
    }


    public static Cursor getAttachChanges(
            String tableName,
            long featureId,
            long attachId,
            int attachOperation)
    {
        String sortOrder = FIELD_ID + " ASC";
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + attachOperation + " ) )" + " AND "
                + getSelectionForSync();

        return query(tableName, selection, sortOrder, null);
    }


    public static boolean isAttachChanges(
            String tableName,
            long featureId,
            long attachId,
            int attachOperation)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + attachOperation + " ) )" + " AND " +
                getSelectionForSync();

        return isRecords(tableName, selection);
    }


    public static boolean isAttachesForDelete(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + CHANGE_OPERATION_DELETE +
                " ) )" + " AND " +
                getSelectionForSync();

        return isRecords(tableName, selection);
    }


    public static long add(
            String tableName,
            long featureId,
            int operation)
    {
        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, featureId);
        values.put(FIELD_OPERATION, operation);
        values.put(FIELD_ATTACH_ID, NOT_FOUND);
        values.put(FIELD_ATTACH_OPERATION, 0);

        return insert(tableName, values);
    }


    public static long add(
            String tableName,
            long featureId,
            long attachId,
            int attachOperation)
    {
        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, featureId);
        values.put(FIELD_OPERATION, CHANGE_OPERATION_ATTACH);
        values.put(FIELD_ATTACH_ID, attachId);
        values.put(FIELD_ATTACH_OPERATION, attachOperation);

        return insert(tableName, values);
    }


    public static int setOperation(
            String tableName,
            long recordId,
            int operation)
    {
        String selection = FIELD_ID + " = " + recordId;

        ContentValues values = new ContentValues();
        values.put(FIELD_OPERATION, operation);

        return update(tableName, values, selection, null);
    }


    public static int setOperation(
            String tableName,
            long recordId,
            long featureId,
            long attachId,
            int attachOperation)
    {
        String selection = FIELD_ID + " = " + recordId + " AND " +
                FIELD_FEATURE_ID + " = " + featureId + " AND " +
                FIELD_ATTACH_ID + " = " + attachId;

        ContentValues values = new ContentValues();
        values.put(FIELD_ATTACH_OPERATION, attachOperation);

        return update(tableName, values, selection, null);
    }


    public static int changeFeatureId(
            String tableName,
            long oldFeatureId,
            long newFeatureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + oldFeatureId;

        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, newFeatureId);

        return update(tableName, values, selection, null);
    }


    public static int changeFeatureIdForAttaches(
            String tableName,
            long oldFeatureId,
            long newFeatureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + oldFeatureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) )";

        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, newFeatureId);

        return update(tableName, values, selection, null);
    }


    public static int removeAllChanges(String tableName)
    {
        String selection = getSelectionForSync();
        return delete(tableName, selection);
    }


    public static int removeAllChangesToLast(
            String tableName,
            long lastRecordId)
    {
        String selection = FIELD_ID + " <= " + lastRecordId + " AND " + getSelectionForSync();
        return delete(tableName, selection);
    }


    public static int removeChanges(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " + getSelectionForSync();
        return delete(tableName, selection);
    }


    public static int removeChangesToLast(
            String tableName,
            long featureId,
            long lastRecordId)
    {
        String selection = FIELD_ID + " <= " + lastRecordId + " AND " +
                FIELD_FEATURE_ID + " = " + featureId + " AND " +
                getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeChanges(
            String tableName,
            long featureId,
            int operation)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + operation + " ) )" + " AND " +
                getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeChangesToLast(
            String tableName,
            long featureId,
            int operation,
            long lastRecordId)
    {
        String selection = FIELD_ID + " <= " + lastRecordId + " AND " +
                FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + operation + " ) )" + " AND " +
                getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeAllAttachChanges(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH + " ) )" + " AND "
                + getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeAllAttachChangesToLast(
            String tableName,
            long featureId,
            long lastRecordId)
    {
        String selection = FIELD_ID + " <= " + lastRecordId + " AND " +
                FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH + " ) )" + " AND "
                + getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeAttachChanges(
            String tableName,
            long featureId,
            long attachId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeAttachChangesToLast(
            String tableName,
            long featureId,
            long attachId,
            long lastRecordId)
    {
        String selection = FIELD_ID + " <= " + lastRecordId + " AND " +
                FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeAttachChanges(
            String tableName,
            long featureId,
            long attachId,
            int attachOperation)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + attachOperation + " ) )" + " AND "
                + getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeAttachChangesToLast(
            String tableName,
            long featureId,
            long attachId,
            int attachOperation,
            long lastRecordId)
    {
        String selection = FIELD_ID + " <= " + lastRecordId + " AND " +
                FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + attachOperation + " ) )" + " AND " +
                getSelectionForSync();

        return delete(tableName, selection);
    }


    public static int removeChangeRecord(
            String tableName,
            long recordId)
    {
        String selection = FIELD_ID + " = " + recordId + " AND " + getSelectionForSync();
        return delete(tableName, selection);
    }


    public static boolean hasFeatureFlags(
            String tableName,
            long featureId)
    {
        return hasFeatureTempFlag(tableName, featureId)
                || hasFeatureNotSyncFlag(tableName, featureId);
    }


    public static boolean hasAttachFlags(
            String tableName,
            long featureId,
            long attachId)
    {
        return hasAttachTempFlag(tableName, featureId, attachId)
                || hasAttachNotSyncFlag(tableName, featureId, attachId);
    }


    public static boolean hasFeatureTempFlag(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_TEMP + " ) )";

        Cursor changesCursor = query(tableName, selection, null, "1");

        boolean res = false;
        if (null != changesCursor) {
            res = changesCursor.getCount() > 0;
            changesCursor.close();
        }

        return res;
    }


    public static boolean haveFeaturesNotSyncFlag(String tableName)
    {
        String selection =
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_NOT_SYNC + " ) )";

        Cursor changesCursor = query(tableName, selection, null, "1");

        boolean res = false;
        if (null != changesCursor) {
            res = changesCursor.getCount() > 0;
            changesCursor.close();
        }

        return res;
    }


    public static boolean hasFeatureNotSyncFlag(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_NOT_SYNC + " ) )";

        Cursor changesCursor = query(tableName, selection, null, "1");

        boolean res = false;
        if (null != changesCursor) {
            res = changesCursor.getCount() > 0;
            changesCursor.close();
        }

        return res;
    }


    public static boolean hasAttachTempFlag(
            String tableName,
            long featureId,
            long attachId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + CHANGE_OPERATION_TEMP + " ) )";

        Cursor changesCursor = query(tableName, selection, null, "1");

        boolean res = false;
        if (null != changesCursor) {
            res = changesCursor.getCount() > 0;
            changesCursor.close();
        }

        return res;
    }


    public static boolean hasAttachNotSyncFlag(
            String tableName,
            long featureId,
            long attachId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + CHANGE_OPERATION_NOT_SYNC + " ) )";

        Cursor changesCursor = query(tableName, selection, null, "1");

        boolean res = false;
        if (null != changesCursor) {
            res = changesCursor.getCount() > 0;
            changesCursor.close();
        }

        return res;
    }


    public static long setFeatureTempFlag(
            String tableName,
            long featureId)
    {
        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, featureId);
        values.put(FIELD_OPERATION, CHANGE_OPERATION_TEMP);
        values.put(FIELD_ATTACH_ID, NOT_FOUND);
        values.put(FIELD_ATTACH_OPERATION, 0);

        return replace(tableName, values);
    }


    public static long setFeatureNotSyncFlag(
            String tableName,
            long featureId)
    {
        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, featureId);
        values.put(FIELD_OPERATION, CHANGE_OPERATION_NOT_SYNC);
        values.put(FIELD_ATTACH_ID, NOT_FOUND);
        values.put(FIELD_ATTACH_OPERATION, 0);

        return replace(tableName, values);
    }


    public static long setAttachTempFlag(
            String tableName,
            long featureId,
            long attachId)
    {
        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, featureId);
        values.put(FIELD_OPERATION, CHANGE_OPERATION_ATTACH);
        values.put(FIELD_ATTACH_ID, attachId);
        values.put(FIELD_ATTACH_OPERATION, CHANGE_OPERATION_TEMP);

        return replace(tableName, values);
    }


    public static long setAttachNotSyncFlag(
            String tableName,
            long featureId,
            long attachId)
    {
        ContentValues values = new ContentValues();
        values.put(FIELD_FEATURE_ID, featureId);
        values.put(FIELD_OPERATION, CHANGE_OPERATION_ATTACH);
        values.put(FIELD_ATTACH_ID, attachId);
        values.put(FIELD_ATTACH_OPERATION, CHANGE_OPERATION_NOT_SYNC);

        return replace(tableName, values);
    }


    public static int deleteFeatureTempFlag(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_TEMP + " ) )";

        return delete(tableName, selection);
    }


    public static int deleteFeatureNotSyncFlag(
            String tableName,
            long featureId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_NOT_SYNC + " ) )";

        return delete(tableName, selection);
    }


    public static int deleteAttachTempFlag(
            String tableName,
            long featureId,
            long attachId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + CHANGE_OPERATION_TEMP + " ) )";

        return delete(tableName, selection);
    }


    public static int deleteAttachNotSyncFlag(
            String tableName,
            long featureId,
            long attachId)
    {
        String selection = FIELD_FEATURE_ID + " = " + featureId + " AND " +
                "( 0 != ( " + FIELD_OPERATION + " & " + CHANGE_OPERATION_ATTACH +
                " ) ) AND " +
                FIELD_ATTACH_ID + " = " + attachId + " AND " +
                "( 0 != ( " + FIELD_ATTACH_OPERATION + " & " + CHANGE_OPERATION_NOT_SYNC + " ) )";

        return delete(tableName, selection);
    }
} public interface GeoConstants
{

    /**
     * Mercator projection constants
     */
    double MERCATOR_MAX = 20037508.34;
    double WGS_LONG_MAX = 180;
    double WGS_LAT_MAX  = 90;

    /**
     * TMS type
     */
    int TMSTYPE_NORMAL = 1;
    int TMSTYPE_OSM    = 2;

    int DEFAULT_MAX_ZOOM = 25;
    int DEFAULT_CACHE_MAX_ZOOM = 18;
    int DEFAULT_MIN_ZOOM = 0;

    /**
     * geometry type
     */
    int GTPoint              = 1;
    int GTLineString         = 2;
    int GTPolygon            = 3;
    int GTMultiPoint         = 4;
    int GTMultiLineString    = 5;
    int GTMultiPolygon       = 6;
    int GTGeometryCollection = 7;
    int GTNone               = 100;
    int GTLinearRing         = 200;

    int GTPointCheck              = 1 << GTPoint;
    int GTLineStringCheck         = 1 << GTLineString;
    int GTPolygonCheck            = 1 << GTPolygon;
    int GTMultiPointCheck         = 1 << GTMultiPoint;
    int GTMultiLineStringCheck    = 1 << GTMultiLineString;
    int GTMultiPolygonCheck       = 1 << GTMultiPolygon;
    int GTGeometryCollectionCheck = 1 << GTGeometryCollection;
    int GTNoneCheck               = 1 << 10;
    int GTAnyCheck                =
            GTMultiPointCheck | GTPointCheck | GTLineStringCheck | GTMultiLineStringCheck |
                    GTPolygonCheck | GTMultiPolygonCheck | GTGeometryCollectionCheck;

    /**
     * geojson see http://geojson.org/geojson-spec.html
     */
    String GEOJSON_CRS_EPSG_4326           = "EPSG:4326";
    String GEOJSON_CRS_WGS84               = "urn:ogc:def:crs:OGC:1.3:CRS84";
    String GEOJSON_CRS_WEB_MERCATOR        = "EPSG:3857";
    String GEOJSON_CRS_EPSG_3857           = "urn:ogc:def:crs:EPSG::3857";
    String GEOJSON_TYPE                    = "type";
    String GEOJSON_ID                      = "id";
    String GEOJSON_FEATURE_ID              = "FEATURE_ID";
    String GEOJSON_CRS                     = "crs";
    String GEOJSON_NAME                    = "name";
    String GEOJSON_PROPERTIES              = "properties";
    String GEOJSON_ATTACHES                = "attaches";
    String GEOJSON_BBOX                    = "bbox";
    String GEOJSON_TYPE_FEATURES           = "features";
    String GEOJSON_GEOMETRY                = "geometry";
    String GEOJSON_GEOMETRIES              = "geometries";
    String GEOJSON_COORDINATES             = "coordinates";
    String GEOJSON_TYPE_Point              = "Point";
    String GEOJSON_TYPE_MultiPoint         = "MultiPoint";
    String GEOJSON_TYPE_LineString         = "LineString";
    String GEOJSON_TYPE_MultiLineString    = "MultiLineString";
    String GEOJSON_TYPE_Polygon            = "Polygon";
    String GEOJSON_TYPE_MultiPolygon       = "MultiPolygon";
    String GEOJSON_TYPE_GeometryCollection = "GeometryCollection";
    String GEOJSON_TYPE_Feature            = "Feature";
    String GEOJSON_TYPE_FeatureCollection  = "FeatureCollection";

    /**
     * field type
     */
    int FTInteger     = 0;
    int FTIntegerList = 1;
    int FTReal        = 2;
    int FTRealList    = 3;
    int FTString      = 4;
    int FTStringList  = 5;
    int FTBinary      = 8;
    int FTDateTime    = 10;
    int FTDate        = 11;
    int FTTime        = 12;

    /**
     * CRS
     */
    int CRS_WGS84        = 4326;
    int CRS_WEB_MERCATOR = 3857;

} public class HttpResponse
{
    protected int    mResponseCode;
    protected String mResponseMessage;
    protected String mResponseBody;
    protected boolean mIsOk = false;

    public HttpResponse(int responseCode)
    {
        mResponseCode = responseCode;
    }

    public HttpResponse(
            int responseCode,
            String responseMessage)
    {
        mResponseCode = responseCode;
        mResponseMessage = responseMessage;
    }

    public HttpResponse(
            int responseCode,
            String responseMessage,
            String responseBody)
    {
        mResponseCode = responseCode;
        mResponseMessage = responseMessage;
        mResponseBody = responseBody;
    }

    public int getResponseCode()
    {
        return mResponseCode;
    }

    public void setResponseCode(int responseCode)
    {
        mResponseCode = responseCode;
    }

    public String getResponseMessage()
    {
        return mResponseMessage;
    }

    public void setResponseMessage(String responseMessage)
    {
        mResponseMessage = responseMessage;
    }

    public String getResponseBody()
    {
        return mResponseBody;
    }

    public void setResponseBody(String responseBody)
    {
        mResponseBody = responseBody;
    }

    public boolean isOk()
    {
        return mIsOk;
    }

    public void setOk(boolean ok)
    {
        mIsOk = ok;
    }
} public class NGException extends Exception {
    public NGException(String message) {
        super(message);
    }

    public NGException(String message, Throwable throwable) {
        super(message, throwable);
    }
} public class NGWUtil
{
    public static String NGWKEY_ID              = "id";
    public static String NGWKEY_GEOM            = "geom";
    public static String NGWKEY_FIELDS          = "fields";
    public static String NGWKEY_SRS             = "srs";
    public static String NGWKEY_EXTENSIONS      = "extensions";
    public static String NGWKEY_NAME            = "name";
    public static String NGWKEY_MIME            = "mime_type";
    public static String NGWKEY_DESCRIPTION     = "description";
    public static String NGWKEY_DISPLAY_NAME    = JSON_DISPLAY_NAME;
    public static String NGWKEY_YEAR            = "year";
    public static String NGWKEY_MONTH           = "month";
    public static String NGWKEY_DAY             = "day";
    public static String NGWKEY_HOUR            = "hour";
    public static String NGWKEY_MINUTE          = "minute";
    public static String NGWKEY_SECOND          = "second";
    public static String NGWKEY_FEATURE_COUNT   = "total_count";
    public static String NGWKEY_KEYNAME         = "keyname";
    public static String NGWKEY_PASSWORD        = "password";
    public static String NGWKEY_CLS             = "cls";
    public static String NGWKEY_VECTOR_LAYER    = "vector_layer";
    public static String NGWKEY_RESOURCE_GROUP  = "resource_group";
    public static String NGWKEY_PARENT          = "parent";
    public static String NGWKEY_GEOMETRY_TYPE   = "geometry_type";
    public static String NGWKEY_DATATYPE        = "datatype";
    public static String NGWKEY_LOOKUP_TABLE    = "lookup_table";
    public static String NGWKEY_RESMETA         = "resmeta";
    public static String NGWKEY_ITEMS           = "items";
    public static String NGUA = "";
    public static String NGID = "";
    public static String UUID = "";

    /**
     * NGW API Functions
     */

    public static String getConnectionCookie(
            AtomicReference<String> reference,
            String login,
            String password)
            throws IOException
    {
        String sUrl = reference.get();
        if (!sUrl.startsWith("http")) {
            sUrl = "http://" + sUrl;
            reference.set(sUrl);
        }

        sUrl += "/login";
        String sPayload = "login=" + login + "&password=" + password;
        final HttpURLConnection conn = NetworkUtil.getHttpConnection("POST", sUrl, null, null);
        if (null == conn) {
            Log.d(TAG, "Error get connection object: " + sUrl);
            return null;
        }
        conn.setInstanceFollowRedirects(false);
        conn.setDefaultUseCaches(false);
        conn.setDoOutput(true);
        conn.connect();

        OutputStream os = conn.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
        writer.write(sPayload);

        writer.flush();
        writer.close();
        os.close();

        int responseCode = conn.getResponseCode();
        if (!(responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM)) {
            Log.d(TAG, "Problem execute post: " + sUrl + " HTTP response: " + responseCode);
            return null;
        }

        String headerName;
        for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) {
            if (headerName.equals("Set-Cookie")) {
                return conn.getHeaderField(i);
            }
        }

        if (!sUrl.startsWith("https")) {
            sUrl = sUrl.replace("http", "https").replace("/login", "");
            reference.set(sUrl);
        }

        return getConnectionCookie(reference, login, password);
    }

    public static String appendix() {
        return "?source=" + NGUA + "&ngid=" + NGID + "&deviceid=" + UUID;
    }

    public static String getServerUrl(String server) {
        if (!server.startsWith("http")) {
            server = "http://" + server;
        }
        return server;//.replace("http://", "https://");
    }

    public static String getFileUploadUrl(String server)
    {
        return getServerUrl(server) + "/api/component/file_upload/upload" + appendix();
    }


    public static String getNgwVersionUrl(String server)
    {
        return getServerUrl(server) + "/api/component/pyramid/pkg_version";
    }


    public static String getNgwUrlResolverUrl(String uuid)
    {
        return "https://groundcontrol.nimbo.nextgis.net/api/instance/" + uuid + "/url";
    }


    public static String getRealNgwUrlFromUuid(String url) throws IOException {
        HttpResponse response = NetworkUtil.get(url, null, null, false);
        if (response.isOk()) {
            return response.mResponseBody;
        }
        return null;
    }


    public static Pair<Integer, Integer> getNgwVersion(Context context, String account) {
        Pair<Integer, Integer> ver = new Pair<>(-1, -1);
        try {
            AccountUtil.AccountData accountData = AccountUtil.getAccountData(context, account);
            ver = NGWUtil.getNgwVersion(accountData.url, accountData.login, accountData.password);
        } catch (IOException | JSONException | NumberFormatException | IllegalStateException ignored) { }

        return ver;
    }


    public static Pair<Integer, Integer> getNgwVersion(
            String url,
            String login,
            String password)
            throws IOException, JSONException, NumberFormatException
    {
        HttpResponse response =
                NetworkUtil.get(NGWUtil.getNgwVersionUrl(url), login, password, false);
        if (response.isOk()) {
            JSONObject verJSONObject = new JSONObject(response.getResponseBody());
            String fullVer = verJSONObject.getString("nextgisweb");
            String[] verParts = fullVer.split("\\.");

            if (verParts.length >= 2) {
                try {
                    Integer major = Integer.parseInt(verParts[0]);
                    Integer minor = Integer.parseInt(verParts[1]);
                    return new Pair<>(major, minor);
                } catch (NumberFormatException ignored) { }
            }

            Log.d(TAG, "BAD format of the NGW version, must start with 'major.minor', obtained: " + fullVer);
        }

        return null;
    }


    /**
     * GeoJSON URL. Get data as GeoJSON
     *
     * @param server
     *         URL to NextGIS Web server
     * @param remoteId
     *         Vector layer resource id
     *
     * @return URL
     */
    public static String getGeoJSONUrl(
            String server,
            long remoteId)
    {
        return getResourceUrl(server, remoteId) + "/geojson";
    }


    /**
     * TMS URL for raster layer
     *
     * @param server
     *         URL
     * @param styleIds
     *         Raster style ids
     *
     * @return URL to TMS for TMSLayer
     */
    public static String getTMSUrl(
            String server,
            Long[] styleIds)
    {
        String ids = "";
        if (styleIds != null && styleIds.length > 0) {
            ids += styleIds[0];
            for (int i = 1; i < styleIds.length; i++) {
                ids += "," + styleIds[i];
            }
        }

        // old url return server + "/resource/" + styleId + "/tms?z={z}&x={x}&y={y}";
        return getServerUrl(server) + "/api/component/render/tile?x={x}&y={y}&z={z}&resource=" + ids;
    }


    /**
     * Resource URL
     *
     * @param server
     *         URL
     * @param remoteId
     *         resource id
     *
     * @return URL to resource
     */
    public static String getResourceUrl(
            String server,
            long remoteId)
    {
        return getBaseUrl(server) + remoteId;
    }


    /**
     * The resource base url
     *
     * @param server
     *         URL
     *
     * @return URL to base resource
     */
    public static String getBaseUrl(String server) {
        return getServerUrl(server) + "/api/resource/";
    }


    /**
     * The form url for vector layer
     *
     * @param server
     *         URL
     * @param remoteId
     *         resource id
     *
     * @return URL to resource
     */
    public static String getFormUrl(String server, long remoteId) {
        return getResourceUrl(server, remoteId) + "/ngfp";
    }


    /**
     * The resource's children url
     *
     * @param server
     *         URL
     *
     * @return URL to base resource
     */
    public static String getResourceChildrenUrl(String server, long resourceId) {
        return getBaseUrl(server) + "?parent=" + resourceId;
    }


    /**
     * Get one row from vector layer
     *
     * @param server
     *         URL
     * @param remoteId
     *         resource id
     * @param featureId
     *         row id
     *
     * @return URL to row
     */
    public static String getFeatureUrl(
            String server,
            long remoteId,
            long featureId)
    {
        return getFeaturesUrl(server, remoteId) + featureId + appendix();
    }


    /**
     * Get the url to JSONArray of features  (NOT GeoJSON!)
     *
     * @param server
     *         URL
     * @param remoteId
     *         vector layer id
     *
     * @return URL
     */
    public static String getFeaturesUrl(
            String server,
            long remoteId)
    {
        return getResourceUrl(server, remoteId) + "/feature/";
    }


    public static String getFeaturesUrl(
            String server,
            long remoteId,
            String where)
    {
        if (TextUtils.isEmpty(where))
            return getFeaturesUrl(server, remoteId);

        return getFeaturesUrl(server, remoteId) + "?" + where;
    }


    public static String getExtent(String server, long remoteId) {
        return getResourceUrl(server, remoteId) + "/extent";
    }


    public static String getTrackedFeaturesUrl(
            String server,
            long remoteId,
            long startDate)
    {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault());
        String start = sdf.format(new Date(startDate));
        return getServerUrl(server) + "/api/vector_layer/" + remoteId + "/diff/?ts_start=" + start;
    }


    public static String getFeatureAttachmentUrl(
            String server,
            long remoteId,
            long featureId)
    {
        return getFeaturesUrl(server, remoteId) + featureId + "/attachment/";
    }


    public static List<Field> getFieldsFromJson(JSONArray fieldsJSONArray)
            throws JSONException
    {
        List<Field> fields = new LinkedList<>();
        for (int i = 0; i < fieldsJSONArray.length(); i++) {
            JSONObject fieldJSONObject = fieldsJSONArray.getJSONObject(i);
            String type = fieldJSONObject.getString("datatype");
            String alias = fieldJSONObject.getString("display_name");
            String name = fieldJSONObject.getString("keyname");

            int nType = LayerUtil.stringToType(type);
            if (Constants.NOT_FOUND != nType) {
                fields.add(new Field(nType, name, alias));
            }
        }
        return fields;
    }


    public static Feature readNGWFeature(
            JsonReader reader,
            List<Field> fields,
            int nSRS)
            throws IOException, IllegalStateException, NumberFormatException, OutOfMemoryError
    {
        final Feature feature = new Feature(Constants.NOT_FOUND, fields);

        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals(NGWUtil.NGWKEY_ID)) {
                feature.setId(reader.nextLong());
            } else if (name.equals(NGWUtil.NGWKEY_GEOM)) {
                String wkt = reader.nextString();
                GeoGeometry geom = GeoGeometryFactory.fromWKT(wkt, nSRS);
                geom.setCRS(nSRS);
                if (nSRS != GeoConstants.CRS_WEB_MERCATOR) {
                    geom.project(GeoConstants.CRS_WEB_MERCATOR);
                }
                feature.setGeometry(geom);
            } else if (name.equals(NGWUtil.NGWKEY_FIELDS)) {
                readNGWFeatureFields(feature, reader, fields);
            } else if (name.equals(NGWUtil.NGWKEY_EXTENSIONS)) {
                if (reader.peek() != JsonToken.NULL) {
                    readNGWFeatureAttachments(feature, reader);
                }
            } else {
                reader.skipValue();
            }
        }
        reader.endObject();
        return feature;
    }


    public static void readNGWFeatureFields(Feature feature, JsonReader reader, List<Field> fields)
            throws IOException, IllegalStateException, NumberFormatException
    {
        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (reader.peek() == JsonToken.NULL) {
                reader.skipValue();
            } else {
                boolean bAdded = false;
                for (Field field : fields) {
                    if (field.getName().equals(name) || field.getName().equals(LayerUtil.normalizeFieldName(name))) {
                        switch (field.getType()) {
                            case GeoConstants.FTReal:
                                feature.setFieldValue(field.getName(), reader.nextDouble());
                                bAdded = true;
                                break;
                            case GeoConstants.FTInteger:
                                feature.setFieldValue(field.getName(), reader.nextInt());
                                bAdded = true;
                                break;
                            case GeoConstants.FTString:
                                feature.setFieldValue(field.getName(), reader.nextString());
                                bAdded = true;
                                break;
                            case GeoConstants.FTDate:
                            case GeoConstants.FTTime:
                            case GeoConstants.FTDateTime:
                                readNGWDate(feature, reader, field.getName());
                                bAdded = true;
                                break;
                            default:
                                break;
                        }
                        break;
                    }
                }
                if (!bAdded) {
                    reader.skipValue();
                }
            }
        }
        reader.endObject();
    }


    private static void readNGWDate(Feature feature, JsonReader reader, String fieldName)
            throws IOException
    {
        reader.beginObject();
        int nYear = 1900;
        int nMonth = 1;
        int nDay = 1;
        int nHour = 0;
        int nMinute = 0;
        int nSecond = 0;
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals(NGWUtil.NGWKEY_YEAR)) {
                nYear = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_MONTH)) {
                nMonth = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_DAY)) {
                nDay = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_HOUR)) {
                nHour = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_MINUTE)) {
                nMinute = reader.nextInt();
            } else if (name.equals(NGWUtil.NGWKEY_SECOND)) {
                nSecond = reader.nextInt();
            } else {
                reader.skipValue();
            }
        }

        TimeZone timeZone = TimeZone.getDefault();
        timeZone.setRawOffset(0); // set to UTC
        Calendar calendar = new GregorianCalendar(timeZone);
        calendar.set(nYear, nMonth - 1, nDay, nHour, nMinute, nSecond);
        calendar.set(Calendar.MILLISECOND, 0); // we must to reset millis
        feature.setFieldValue(fieldName, calendar.getTimeInMillis());

        reader.endObject();
    }


    private static void readNGWFeatureAttachments(Feature feature, JsonReader reader)
            throws IOException
    {
        //add extensions
        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals("attachment") && reader.peek() != JsonToken.NULL) {
                reader.beginArray();
                while (reader.hasNext()) {
                    readNGWFeatureAttachment(feature, reader);
                }
                reader.endArray();
            } else {
                reader.skipValue();
            }
        }

        reader.endObject();
    }


    private static void readNGWFeatureAttachment(Feature feature, JsonReader reader)
            throws IOException
    {
        reader.beginObject();
        String attachId = "";
        String name = "";
        String mime = "";
        String descriptionText = "";
        while (reader.hasNext()) {
            String keyName = reader.nextName();
            if (reader.peek() == JsonToken.NULL) {
                reader.skipValue();
                continue;
            }

            if (keyName.equals(NGWUtil.NGWKEY_ID)) {
                attachId += reader.nextLong();
            } else if (keyName.equals(NGWUtil.NGWKEY_NAME)) {
                name += reader.nextString();
            } else if (keyName.equals(NGWUtil.NGWKEY_MIME)) {
                mime += reader.nextString();
            } else if (keyName.equals(NGWUtil.NGWKEY_DESCRIPTION)) {
                descriptionText += reader.nextString();
            } else {
                reader.skipValue();
            }
        }
        AttachItem item = new AttachItem(attachId, name, mime, descriptionText);
        feature.addAttachment(item);

        reader.endObject();
    }


    public static List<Feature> jsonToFeatures(
            JSONArray featuresJSONArray,
            List<Field> fields,
            int nSRS,
            IProgressor progressor)
            throws JSONException
    {
        List<Feature> features = new LinkedList<>();
        if (null != progressor) {
            progressor.setMax(featuresJSONArray.length());
            progressor.setIndeterminate(false);
        }

        for (int i = 0; i < featuresJSONArray.length(); i++) {
            JSONObject featureJSONObject = featuresJSONArray.getJSONObject(i);

            if (null != progressor) {
                progressor.setValue(i);
            }

            long id = featureJSONObject.getLong(NGWUtil.NGWKEY_ID);
            String wkt = featureJSONObject.getString(NGWUtil.NGWKEY_GEOM);
            JSONObject fieldsJSONObject = featureJSONObject.getJSONObject(NGWUtil.NGWKEY_FIELDS);
            Feature feature = new Feature(id, fields);
            GeoGeometry geom = GeoGeometryFactory.fromWKT(wkt, nSRS);
            if (null == geom)
                continue;

            geom.setCRS(nSRS);
            if (nSRS != GeoConstants.CRS_WEB_MERCATOR)
                geom.project(GeoConstants.CRS_WEB_MERCATOR);
            if (!geom.isValid())
                continue;

            feature.setGeometry(geom);

            for (Field field : fields) {
                if (field.getType() == GeoConstants.FTDateTime ||
                        field.getType() == GeoConstants.FTDate ||
                        field.getType() == GeoConstants.FTTime) {
                    if (!fieldsJSONObject.isNull(field.getName())) {
                        JSONObject dateJson = fieldsJSONObject.getJSONObject(field.getName());
                        int nYear = 1900;
                        int nMonth = 1;
                        int nDay = 1;
                        int nHour = 0;
                        int nMinute = 0;
                        int nSec = 0;
                        if (dateJson.has(NGWKEY_YEAR)) {
                            nYear = dateJson.getInt(NGWKEY_YEAR);
                        }
                        if (dateJson.has(NGWKEY_MONTH)) {
                            nMonth = dateJson.getInt(NGWKEY_MONTH);
                        }
                        if (dateJson.has(NGWKEY_DAY)) {
                            nDay = dateJson.getInt(NGWKEY_DAY);
                        }
                        if (dateJson.has(NGWKEY_HOUR)) {
                            nHour = dateJson.getInt(NGWKEY_HOUR);
                        }
                        if (dateJson.has(NGWKEY_MINUTE)) {
                            nMinute = dateJson.getInt(NGWKEY_MINUTE);
                        }
                        if (dateJson.has(NGWKEY_SECOND)) {
                            nSec = dateJson.getInt(NGWKEY_SECOND);
                        }

                        TimeZone timeZone = TimeZone.getDefault();
                        timeZone.setRawOffset(0); // set to UTC
                        Calendar calendar = new GregorianCalendar(timeZone);
                        calendar.set(nYear, nMonth - 1, nDay, nHour, nMinute, nSec);
                        calendar.set(Calendar.MILLISECOND, 0); // we must to reset millis
                        feature.setFieldValue(field.getName(), calendar.getTimeInMillis());
                    }
                } else {
                    if (!fieldsJSONObject.isNull(field.getName())) {
                        feature.setFieldValue(
                                field.getName(), fieldsJSONObject.get(field.getName()));
                    }
                }
            }

            //add extensions
            if (featureJSONObject.has("extensions")) {
                JSONObject ext = featureJSONObject.getJSONObject("extensions");
                //get attachment & description
                if (!ext.isNull("attachment")) {
                    JSONArray attachment = ext.getJSONArray("attachment");
                    for (int j = 0; j < attachment.length(); j++) {
                        JSONObject jsonAttachmentDetails = attachment.getJSONObject(j);
                        String attachId = "" + jsonAttachmentDetails.getLong(Constants.JSON_ID_KEY);
                        String name = jsonAttachmentDetails.getString(Constants.JSON_NAME_KEY);
                        String mime = jsonAttachmentDetails.getString("mime_type");
                        String descriptionText = jsonAttachmentDetails.getString("description");
                        AttachItem item = new AttachItem(attachId, name, mime, descriptionText);
                        feature.addAttachment(item);
                    }
                }
            }

            features.add(feature);
        }
        return features;
    }


    public static boolean signUp(
            String server,
            String login,
            String password,
            String displayName,
            String description)
    {
        server += server.endsWith("/") ? "" : "/";
        server += "api/component/auth/register";
        server = getServerUrl(server);

        JSONObject payload = new JSONObject();
        try {
            payload.put(NGWKEY_KEYNAME, login);
            payload.put(NGWKEY_PASSWORD, password);
            payload.put(NGWKEY_DISPLAY_NAME, displayName == null ? login : displayName);
            payload.put(NGWKEY_DESCRIPTION, description);

            HttpResponse response =
                    NetworkUtil.post(server, payload.toString(), null, null, false);
            return response.isOk() && new JSONObject(response.getResponseBody()).has(JSON_ID_KEY);
        } catch (JSONException | IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    public static boolean getResourceByKey(Context context, INGWResource resource, Map<String, Resource> keys) {
        if (resource instanceof Connection) {
            Connection connection = (Connection) resource;
            connection.loadChildren();
        } else if (resource instanceof ResourceGroup) {
            ResourceGroup resourceGroup = (ResourceGroup) resource;
            resourceGroup.loadChildren();
        }

        for (int i = 0; i < resource.getChildrenCount(); ++i) {
            INGWResource childResource = resource.getChild(i);

            if (keys.containsKey(childResource.getKey()) && childResource instanceof Resource) {
                Resource ngwResource = (Resource) childResource;
                keys.put(ngwResource.getKey(), ngwResource);
            }

            boolean bIsFill = true;
            for (Map.Entry<String, Resource> entry : keys.entrySet()) {
                if (entry.getValue() == null) {
                    bIsFill = false;
                    break;
                }
            }

            if (bIsFill) {
                return true;
            }

            if (getResourceByKey(context, childResource, keys)) {
                return true;
            }
        }

        boolean bIsFill = true;

        for (Map.Entry<String, Resource> entry : keys.entrySet()) {
            if (entry.getValue() == null) {
                bIsFill = false;
                break;
            }
        }

        return bIsFill;
    }

    public static HttpResponse createNewResource(
            Context context,
            Connection connection,
            JSONObject json)
    {
        HttpResponse result;
        try {
            AccountUtil.AccountData accountData =
                    AccountUtil.getAccountData(context, connection.getName());
            result = NetworkUtil.post(getBaseUrl(accountData.url), json.toString(),
                    accountData.login, accountData.password, false);
        } catch (IOException e) {
            e.printStackTrace();
            result = new HttpResponse(500);
        } catch (IllegalStateException e) {
            e.printStackTrace();
            result = new HttpResponse(401);
        }

        return result;
    }

    public static HttpResponse createNewGroup(
            Context context,
            Connection connection,
            long parentId,
            String name,
            String keyName)
    {
        JSONObject payload = new JSONObject();
        try {
            JSONObject resource = new JSONObject();
            resource.put(NGWKEY_CLS, NGWKEY_RESOURCE_GROUP);
            JSONObject id = new JSONObject();
            id.put(JSON_ID_KEY, parentId);
            resource.put(NGWKEY_PARENT, id);
            resource.put(NGWKEY_DISPLAY_NAME, name);
            if (keyName != null) {
                resource.put(NGWKEY_KEYNAME, keyName);
            }
            payload.put(JSON_RESOURCE_KEY, resource);
        } catch (JSONException e) {
            e.printStackTrace();
            return new HttpResponse(500);
        }

        return createNewResource(context, connection, payload);
    }

    public static HttpResponse createNewLayer(
            Connection connection,
            VectorLayer layer,
            long parentId,
            String keyName)
    {
        JSONObject payload = new JSONObject();
        try {
            JSONObject resource = new JSONObject();
            resource.put(NGWKEY_CLS, NGWKEY_VECTOR_LAYER);
            JSONObject id = new JSONObject();
            id.put(JSON_ID_KEY, parentId);
            resource.put(NGWKEY_PARENT, id);
            resource.put(JSON_DISPLAY_NAME, layer.getName());
            if (keyName != null) {
                resource.put(NGWKEY_KEYNAME, keyName);
            }
            payload.put(JSON_RESOURCE_KEY, resource);

            JSONObject vectorLayer = new JSONObject();
            JSONObject srs = new JSONObject();
            srs.put(JSON_ID_KEY, GeoConstants.CRS_WEB_MERCATOR);
            vectorLayer.put(NGWKEY_SRS, srs);
            vectorLayer.put(
                    NGWKEY_GEOMETRY_TYPE, GeoGeometryFactory.typeToString(layer.getGeometryType()));
            JSONArray fields = new JSONArray();
            for (Field field : layer.getFields()) {
                JSONObject current = new JSONObject();
                current.put(NGWKEY_KEYNAME, field.getName());
                current.put(NGWKEY_DISPLAY_NAME, field.getAlias());
                current.put(NGWKEY_DATATYPE, LayerUtil.typeToString(field.getType()));
                fields.put(current);
            }
            vectorLayer.put(NGWKEY_FIELDS, fields);
            payload.put(NGWKEY_VECTOR_LAYER, vectorLayer);
        } catch (JSONException e) {
            e.printStackTrace();
            return new HttpResponse(500);
        }

        return createNewResource(layer.getContext(), connection, payload);
    }

    public static HttpResponse createNewLookupTable(
            Connection connection,
            NGWLookupTable table,
            long parentId,
            String keyName)
    {
        JSONObject payload = new JSONObject();
        try {
            JSONObject resource = new JSONObject();
            resource.put(NGWKEY_CLS, NGWKEY_LOOKUP_TABLE);
            JSONObject id = new JSONObject();
            id.put(JSON_ID_KEY, parentId);
            resource.put(NGWKEY_PARENT, id);
            resource.put(JSON_DISPLAY_NAME, table.getName());
            if (keyName != null) {
                resource.put(NGWKEY_KEYNAME, keyName);
            }
            payload.put(JSON_RESOURCE_KEY, resource);

            JSONObject itemsResmeta = new JSONObject();
            itemsResmeta.put(NGWKEY_ITEMS, new JSONObject());
            payload.put(NGWKEY_RESMETA, itemsResmeta);

            JSONObject jsonData = new JSONObject();
            Map<String, String> data = table.getData();
            if (data != null) {
                for (Map.Entry<String, String> entry : data.entrySet()) {
                    jsonData.put(entry.getKey(), entry.getValue());
                }
            }
            JSONObject items = new JSONObject();
            items.put(NGWKEY_ITEMS, jsonData);
            payload.put(NGWKEY_LOOKUP_TABLE, items);
        } catch (JSONException e) {
            e.printStackTrace();
            return new HttpResponse(500);
        }

        return createNewResource(table.getContext(), connection, payload);
    }
} public class NetworkUtil
{
    private static final String IP_ADDRESS = "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
            + "|[1-9][0-9]|[0-9]))(:[0-9]{1,5})?";
    public static final String URL_PATTERN = "^(?i)((ftp|https?)://)?(([\\da-z.-]+)\\.([a-z.]{2,6})|" + IP_ADDRESS + ")(:[0-9]{1,5})?(/\\S*)?$";

    public final ConnectivityManager mConnectionManager;
    public final TelephonyManager    mTelephonyManager;
    protected       long                mLastCheckTime;
    protected       Context             mContext;

    public final static int TIMEOUT_CONNECTION = 10000;
    public final static int TIMEOUT_SOCKET = 240000; // 180 sec

    public final static int ERROR_AUTH                = -401;
    public final static int ERROR_NETWORK_UNAVAILABLE = -1;
    public final static int ERROR_CONNECT_FAILED      = 0;
    public final static int ERROR_DOWNLOAD_DATA       = 1;

    public final static String HTTP_GET    = "GET";
    public final static String HTTP_POST   = "POST";
    public final static String HTTP_PUT    = "PUT";
    public final static String HTTP_DELETE = "DELETE";


    public NetworkUtil(Context context)
    {
        mConnectionManager =
                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        mLastCheckTime = Constants.NOT_FOUND;
        mContext = context;
    }


    public synchronized boolean isNetworkAvailable()
    {
        if (mConnectionManager == null) {
            return false;
        }

        NetworkInfo info = mConnectionManager.getActiveNetworkInfo();
        if (info == null) {
            return false;
        }

        return info.isConnected();
    }

    public static HttpURLConnection getProperConnection(String targetURL) throws IOException {
        URL url = new URL(targetURL);
        // Open a HTTP connection to the URL
        if (targetURL.startsWith("https://"))
            return (HttpsURLConnection) url.openConnection();
        else
            return (HttpURLConnection) url.openConnection();
    }


    public static HttpURLConnection getHttpConnection(
            String method,
            String targetURL,
            String username,
            String password)
            throws IOException {
        HttpURLConnection conn = getProperConnection(targetURL);

        String basicAuth = getHTTPBaseAuth(username, password);
        if (null != basicAuth) {
            conn.setRequestProperty("Authorization", basicAuth);
        }

        return getHttpConnection(method, targetURL, conn);
    }

    public static HttpURLConnection getHttpConnection(
            String method,
            String targetURL,
            String auth)
            throws IOException {
        HttpURLConnection conn = getProperConnection(targetURL);

        if (null != auth) {
            conn.setRequestProperty("Authorization", auth);
        }

        return getHttpConnection(method, targetURL, conn);
    }


    public static HttpURLConnection getHttpConnection(
            String method,
            String targetURL,
            HttpURLConnection conn)
            throws IOException {
        conn.setRequestProperty("User-Agent", Constants.APP_USER_AGENT);

        // Allow Inputs
        conn.setDoInput(true);
        // Don't use a cached copy.
        conn.setUseCaches(false);
        // Use a post method.
        if (method.length() > 0)
            conn.setRequestMethod(method);

        conn.setConnectTimeout(TIMEOUT_CONNECTION);
        conn.setReadTimeout(TIMEOUT_SOCKET);
        conn.setRequestProperty("Accept", "*/*");

        return isValidUri(targetURL) ? conn : null;
    }


    public static boolean isValidUri(String url) {
        Pattern pattern = Pattern.compile(URL_PATTERN);
        Matcher match = pattern.matcher(url);
        return match.matches();
    }

    public static String getHTTPBaseAuth(String username, String password){
        if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
            return "Basic " + Base64.encodeToString(
                    (username + ":" + password).getBytes(), Base64.NO_WRAP);
        }
        return null;
    }

    public static String responseToString(final InputStream is)
            throws IOException
    {
        if (is == null) {
            return null;
        }

        byte[] buffer = new byte[Constants.IO_BUFFER_SIZE];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FileUtil.copyStream(is, baos, buffer, Constants.IO_BUFFER_SIZE);
        byte[] bytesReceived = baos.toByteArray();
        baos.close();
        is.close();

        return new String(bytesReceived);
    }

    public static void getStream(
            String targetURL,
            String username,
            String password,
            OutputStream outputStream)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection("GET", targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get stream: " + targetURL);
            throw new IOException("Connection is null");
        }
        int responseCode = conn.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            getStream(targetURL, username, password, outputStream);
            return;
        }
        if (responseCode != HttpURLConnection.HTTP_OK) {
            if(Constants.DEBUG_MODE)
                Log.d(TAG, "Problem execute getStream: " + targetURL + " HTTP response: " +
                    responseCode + " username: " + username);
            throw new IOException("Response code is " + responseCode);
        }

        byte data[] = new byte[Constants.IO_BUFFER_SIZE];
        InputStream is = conn.getInputStream();
        FileUtil.copyStream(is, outputStream, data, Constants.IO_BUFFER_SIZE);
        outputStream.close();
    }

    protected static HttpResponse getHttpResponse(
            HttpURLConnection conn,
            boolean readErrorResponseBody)
            throws IOException
    {
        String method = conn.getRequestMethod();
        int code = conn.getResponseCode();
        String message = conn.getResponseMessage();
        HttpResponse response = new HttpResponse(code, message);

        if (code == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            if (method.equals("PUT") || method.equals("POST")) {
                return response;
            }
            String target = conn.getURL().toString().replace("http", "https");
            String auth = conn.getRequestProperty("Authorization");
            HttpURLConnection connection = getHttpConnection(conn.getRequestMethod(), target, auth);
            return getHttpResponse(connection, readErrorResponseBody);
        }

        if (!(code == HttpURLConnection.HTTP_OK
                || (code == HttpURLConnection.HTTP_CREATED && method.equals(HTTP_POST)))) {
            if (Constants.DEBUG_MODE) {
                String url = conn.getURL().toExternalForm();
                if ("Keep-Alive".equals(conn.getRequestProperty("Connection")))
                    method = "postFile(), targetURL";
                Log.d(TAG, "Problem execute " + method + ": " + url + " HTTP response: " + code);
            }
            if (readErrorResponseBody)
                response.setResponseBody(responseToString(conn.getErrorStream()));
            return response;
        }

        String body = responseToString(conn.getInputStream());
        if (null == body) {
            response.setResponseCode(ERROR_DOWNLOAD_DATA);
            response.setResponseMessage(null);
            return response;
        }

        response.setResponseBody(body);
        response.setOk(true);
        return response;
    }

    public static HttpResponse get(
            String targetURL,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException {
        final HttpURLConnection conn = getHttpConnection(HTTP_GET, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        return getHttpResponse(conn, readErrorResponseBody);
    }


    public static HttpResponse post(
            String targetURL,
            String payload,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection(HTTP_POST, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        conn.setRequestProperty("Content-type", "application/json");
        // Allow Outputs
        conn.setDoOutput(true);

        OutputStream os = conn.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
        writer.write(payload);

        writer.flush();
        writer.close();
        os.close();

        HttpResponse response = getHttpResponse(conn, readErrorResponseBody);
        if (response.mResponseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            return post(targetURL, payload, username, password, readErrorResponseBody);
        }
        return response;
    }

    public static HttpResponse delete(
            String targetURL,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection(HTTP_DELETE, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        return getHttpResponse(conn, readErrorResponseBody);
    }


    public static HttpResponse put(
            String targetURL,
            String payload,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final HttpURLConnection conn = getHttpConnection(HTTP_PUT, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        conn.setRequestProperty("Content-type", "application/json");
        // Allow Outputs
        conn.setDoOutput(true);

        OutputStream os = conn.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
        writer.write(payload);

        writer.flush();
        writer.close();
        os.close();

        HttpResponse response = getHttpResponse(conn, readErrorResponseBody);
        if (response.mResponseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            return put(targetURL, payload, username, password, readErrorResponseBody);
        }
        return response;
    }


    public static HttpResponse postFile(
            String targetURL,
            String fileName,
            File file,
            String fileMime,
            String username,
            String password,
            boolean readErrorResponseBody)
            throws IOException
    {
        final String lineEnd = "\r\n";
        final String twoHyphens = "--";
        final String boundary = "**nextgis**";

        //------------------ CLIENT REQUEST
        FileInputStream fileInputStream = new FileInputStream(file);
        // open a URL connection to the Servlet

        HttpURLConnection conn = getHttpConnection(HTTP_POST, targetURL, username, password);
        if (null == conn) {
            if (Constants.DEBUG_MODE)
                Log.d(TAG, "Error get connection object: " + targetURL);
            return new HttpResponse(ERROR_CONNECT_FAILED);
        }
        conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
        // Allow Outputs
        conn.setDoOutput(true);

        DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
        dos.writeBytes(twoHyphens + boundary + lineEnd);
        dos.writeBytes(
                "Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"" +
                lineEnd);

        if (!TextUtils.isEmpty(fileMime)) {
            dos.writeBytes("Content-Type: " + fileMime + lineEnd);
        }

        dos.writeBytes(lineEnd);

        byte[] buffer = new byte[Constants.IO_BUFFER_SIZE];
        FileUtil.copyStream(fileInputStream, dos, buffer, Constants.IO_BUFFER_SIZE);

        dos.writeBytes(lineEnd);
        dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
        fileInputStream.close();
        dos.flush();
        dos.close();

        HttpResponse response = getHttpResponse(conn, readErrorResponseBody);
        if (response.mResponseCode == HttpURLConnection.HTTP_MOVED_PERM && conn.getURL().getProtocol().equals("http")) {
            targetURL = targetURL.replace("http", "https");
            return postFile(targetURL, fileName, file, fileMime, username, password, readErrorResponseBody);
        }
        return response;
    }

    public static String trimSlash(String url) {
        return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
    }

    public static String getError(
            Context context,
            int responseCode)
    {
        switch (responseCode) {
            case ERROR_AUTH:
                return context.getString(R.string.error_auth);
            case ERROR_NETWORK_UNAVAILABLE:
                return context.getString(R.string.error_network_unavailable);
            case ERROR_CONNECT_FAILED:
                return context.getString(R.string.error_connect_failed);
            case ERROR_DOWNLOAD_DATA:
                return context.getString(R.string.error_download_data);
            case HttpURLConnection.HTTP_UNAUTHORIZED:
                return context.getString(R.string.error_401);
            case HttpURLConnection.HTTP_FORBIDDEN:
                return context.getString(R.string.error_403);
            case HttpURLConnection.HTTP_NOT_FOUND:
                return context.getString(R.string.error_404);
            case HttpURLConnection.HTTP_INTERNAL_ERROR:
                return context.getString(R.string.error_500);
            default:
                return context.getString(R.string.error_500);
        }
    }
} public class ProgressBufferedInputStream extends BufferedInputStream{
    protected int mTotalStreamSize;
    protected int mCurrentReadSize;
    public ProgressBufferedInputStream(InputStream in, int totalStreamSize) {
        super(in, Constants.IO_BUFFER_SIZE);
        mTotalStreamSize = totalStreamSize;
        mCurrentReadSize = 0;
    }

    public ProgressBufferedInputStream(InputStream in, int size, int totalStreamSize) {
        super(in, size);
        mTotalStreamSize = totalStreamSize;
        mCurrentReadSize = 0;
    }

    @Override
    public synchronized int available() throws IOException {
        return mTotalStreamSize - mCurrentReadSize;
    }

    @Override
    public synchronized int read()
            throws IOException
    {
        int b = super.read();
        if (b >= 0)
            mCurrentReadSize += 1;
        return b;
    }

    @Override
    public synchronized int read(byte[] b, int off, int len)
            throws IOException
    {
        int n = super.read(b, off, len);
        if (n > 0)
            mCurrentReadSize += n;
        return n;
    }

    @Override
    public int read(byte[] buffer) throws IOException {
        int n = super.read(buffer);
        if (n > 0)
            mCurrentReadSize += n;
        return n;
    }

    @Override
    public synchronized long skip(long skip)
            throws IOException
    {
        long n = super.skip(skip);
        if (n > 0)
            mCurrentReadSize += n;
        return n;
    }

    @Override
    public synchronized void reset()
            throws IOException
    {
    /* A call to reset can still succeed if mark is not supported, but the
     * resulting stream position is undefined, so it's not allowed here. */
        if (!markSupported())
            throw new IOException("Mark not supported.");
        super.reset();
        mCurrentReadSize = markpos;
    }
} public interface SettingsConstants
{
    String KEY_PREF_MAP                     = "map";
    String KEY_PREF_MAP_PATH                = "map_path";
    String KEY_PREF_LAST_SYNC_TIMESTAMP     = "last_sync_timestamp";
    String KEY_PREF_LOCATION_SOURCE         = "location_source";
    String KEY_PREF_LOCATION_MIN_TIME       = "location_min_time";
    String KEY_PREF_LOCATION_MIN_DISTANCE   = "location_min_distance";
    String KEY_PREF_LOCATION_ACCURATE_COUNT = "accurate_max_count";
    String KEY_PREF_LOCATION_ACCURATE_TIME  = "accurate_max_time";
    String KEY_PREF_LOCATION_ACCURATE_CE    = "accurate_ce";
    String KEY_PREF_TRACKS_MIN_TIME         = "tracks_min_time";
    String KEY_PREF_TRACKS_MIN_DISTANCE     = "tracks_min_distance";
    String KEY_PREF_TRACKS_SOURCE           = "tracks_location_source";
    String KEY_PREF_TRACK_RESTORE           = "track_restore";
    String KEY_PREF_TRACK_SEND              = "track_send";
    String KEY_PREF_UNITS                   = "preferred_units";
} public final static int NGWResourceTypePostgisLayer      = 1 << 2; public static String appendix() {
    return "?source=" + NGUA + "&ngid=" + NGID + "&deviceid=" + UUID;
}
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.text.TextUtils;
import android.util.JsonReader;
import android.util.Log;
import android.util.Pair;
import com.hypertrack.hyperlog.HyperLog;
import com.nextgis.maplib.R;
import com.nextgis.maplib.api.IGISApplication;
import com.nextgis.maplib.api.INGWLayer;
import com.nextgis.maplib.api.IProgressor;
import com.nextgis.maplib.datasource.Feature;
import com.nextgis.maplib.datasource.Field;
import com.nextgis.maplib.datasource.GeoGeometry;
import com.nextgis.maplib.datasource.GeoGeometryFactory;
import com.nextgis.maplib.datasource.ngw.Connection;
import com.nextgis.maplib.datasource.ngw.SyncAdapter;
import com.nextgis.maplib.util.AccountUtil;
import com.nextgis.maplib.util.AttachItem;
import com.nextgis.maplib.util.Constants;
import com.nextgis.maplib.util.DatabaseContext;
import com.nextgis.maplib.util.FeatureChanges;
import com.nextgis.maplib.util.GeoConstants;
import com.nextgis.maplib.util.HttpResponse;
import com.nextgis.maplib.util.NGException;
import com.nextgis.maplib.util.NGWUtil;
import com.nextgis.maplib.util.NetworkUtil;
import com.nextgis.maplib.util.ProgressBufferedInputStream;
import com.nextgis.maplib.util.SettingsConstants;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import static com.nextgis.maplib.datasource.ngw.Connection.NGWResourceTypePostgisLayer;
import static com.nextgis.maplib.util.Constants.CHANGE_OPERATION_ATTACH;
import static com.nextgis.maplib.util.Constants.CHANGE_OPERATION_TEMP;
import static com.nextgis.maplib.util.Constants.FIELD_ATTACH_ID;
import static com.nextgis.maplib.util.Constants.FIELD_ATTACH_OPERATION;
import static com.nextgis.maplib.util.Constants.FIELD_FEATURE_ID;
import static com.nextgis.maplib.util.Constants.FIELD_ID;
import static com.nextgis.maplib.util.Constants.FIELD_OPERATION;
import static com.nextgis.maplib.util.Constants.MIN_LOCAL_FEATURE_ID;
import static com.nextgis.maplib.util.Constants.TAG;
import static com.nextgis.maplib.util.Constants.URI_ATTACH;
import static com.nextgis.maplib.util.Constants.URI_CHANGES;
import static com.nextgis.maplib.util.NGWUtil.appendix;
/*
 * Project:  NextGIS Mobile
 * Purpose:  Mobile GIS for Android.
 * Author:   Dmitry Baryshnikov (aka Bishop), bishop.dev@gmail.com
 * Author:   NikitaFeodonit, nfeodonit@yandex.com
 * Author:   Stanislav Petriakov, becomeglory@gmail.com
 * *****************************************************************************
 * Copyright (c) 2015-2021 NextGIS, info@nextgis.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nextgis.maplib.map;








public class NGWVectorLayer
        extends VectorLayer
        implements INGWLayer
{
    protected static final String JSON_ACCOUNT_KEY           = "account";
    protected static final String JSON_NGW_VERSION_MAJOR_KEY = "ngw_version_major";
    protected static final String JSON_NGW_VERSION_MINOR_KEY = "ngw_version_minor";
    protected static final String JSON_SYNC_TYPE_KEY         = "sync_type";
    protected static final String JSON_NGWLAYER_TYPE_KEY     = "ngw_layer_type";
    protected static final String JSON_SERVERWHERE_KEY       = "server_where";
    protected static final String JSON_TRACKED_KEY           = "tracked";
    protected static final String JSON_SYNC_DIRECTION_KEY    = "sync_direction";

    protected static final int TYPE_CHANGES_TABLE     = 125;
    protected static final int TYPE_CHANGES_FEATURE   = 126;
    protected static final int TYPE_CHANGES_ATTACH    = 127;
    protected static final int TYPE_CHANGES_ATTACH_ID = 128;

    protected static final int DIRECTION_TO = 1;
    protected static final int DIRECTION_FROM = 2;
    protected static final int DIRECTION_BOTH = 3;

    protected static boolean mIsAddedToUriMatcher = false;

    protected NetworkUtil mNet;

    protected int mNgwVersionMajor = Constants.NOT_FOUND;
    protected int mNgwVersionMinor = Constants.NOT_FOUND;

    protected String mAccountName;
    protected long   mRemoteId;
    protected int    mSyncType;
    protected int    mNGWLayerType;
    protected int    mCRS = GeoConstants.CRS_WEB_MERCATOR;