Example usage for android.content ContentValues containsKey

List of usage examples for android.content ContentValues containsKey

Introduction

In this page you can find the example usage for android.content ContentValues containsKey.

Prototype

public boolean containsKey(String key) 

Source Link

Document

Returns true if this object has the named value.

Usage

From source file:com.csipsimple.db.DBProvider.java

@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    int count;//  ww  w  .j  a  v a 2s . c om
    String finalWhere;
    int matched = URI_MATCHER.match(uri);

    List<String> possibles = getPossibleFieldsForType(matched);
    checkSelection(possibles, where);

    switch (matched) {
    case ACCOUNTS:
        count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, where, whereArgs);
        break;
    case ACCOUNTS_ID:
        finalWhere = DatabaseUtilsCompat
                .concatenateWhere(SipProfile.FIELD_ID + " = " + ContentUris.parseId(uri), where);
        count = db.update(SipProfile.ACCOUNTS_TABLE_NAME, values, finalWhere, whereArgs);
        break;
    case CALLLOGS:
        count = db.update(SipManager.CALLLOGS_TABLE_NAME, values, where, whereArgs);
        break;
    case CALLLOGS_ID:
        finalWhere = DatabaseUtilsCompat.concatenateWhere(CallLog.Calls._ID + " = " + ContentUris.parseId(uri),
                where);
        count = db.update(SipManager.CALLLOGS_TABLE_NAME, values, finalWhere, whereArgs);
        break;
    case FILTERS:
        count = db.update(SipManager.FILTERS_TABLE_NAME, values, where, whereArgs);
        break;
    case FILTERS_ID:
        finalWhere = DatabaseUtilsCompat.concatenateWhere(Filter._ID + " = " + ContentUris.parseId(uri), where);
        count = db.update(SipManager.FILTERS_TABLE_NAME, values, finalWhere, whereArgs);
        break;
    case MESSAGES:
        count = db.update(SipMessage.MESSAGES_TABLE_NAME, values, where, whereArgs);
        break;
    case MESSAGES_ID:
        finalWhere = DatabaseUtilsCompat
                .concatenateWhere(SipMessage.FIELD_ID + " = " + ContentUris.parseId(uri), where);
        count = db.update(SipMessage.MESSAGES_TABLE_NAME, values, where, whereArgs);
        break;
    case ACCOUNTS_STATUS_ID:
        long id = ContentUris.parseId(uri);
        synchronized (profilesStatus) {
            SipProfileState ps = new SipProfileState();
            if (profilesStatus.containsKey(id)) {
                ContentValues currentValues = profilesStatus.get(id);
                ps.createFromContentValue(currentValues);
            }
            ps.createFromContentValue(values);
            ContentValues cv = ps.getAsContentValue();
            cv.put(SipProfileState.ACCOUNT_ID, id);
            profilesStatus.put(id, cv);
            Log.d(THIS_FILE, "Updated " + cv);
        }
        count = 1;
        break;
    default:
        throw new IllegalArgumentException(UNKNOWN_URI_LOG + uri);
    }

    getContext().getContentResolver().notifyChange(uri, null);

    long rowId = -1;
    if (matched == ACCOUNTS_ID || matched == ACCOUNTS_STATUS_ID) {
        rowId = ContentUris.parseId(uri);
    }
    if (rowId >= 0) {
        if (matched == ACCOUNTS_ID) {
            // Don't broadcast if we only changed wizard or only changed priority
            boolean doBroadcast = true;
            if (values.size() == 1) {
                if (values.containsKey(SipProfile.FIELD_WIZARD)) {
                    doBroadcast = false;
                } else if (values.containsKey(SipProfile.FIELD_PRIORITY)) {
                    doBroadcast = false;
                }
            }
            if (doBroadcast) {
                broadcastAccountChange(rowId);
            }
        } else if (matched == ACCOUNTS_STATUS_ID) {
            broadcastRegistrationChange(rowId);
        }
    }
    if (matched == FILTERS || matched == FILTERS_ID) {
        Filter.resetCache();
    }

    return count;
}

From source file:com.android.exchange.SyncManager.java

/**
 * See if we need to change the syncInterval for any of our PIM mailboxes based on changes
 * to settings in the AccountManager (sync settings).
 * This code is called 1) when SyncManager starts, and 2) when SyncManager is running and there
 * are changes made (this is detected via a SyncStatusObserver)
 *//*from  www .  j  a v a  2  s  .com*/
private void updatePIMSyncSettings(Account providerAccount, int mailboxType, String authority) {
    ContentValues cv = new ContentValues();
    long mailboxId = Mailbox.findMailboxOfType(this, providerAccount.mId, mailboxType);
    // Presumably there is one, but if not, it's ok.  Just move on...
    if (mailboxId != Mailbox.NO_MAILBOX) {
        // Create an AccountManager style Account
        android.accounts.Account acct = new android.accounts.Account(providerAccount.mEmailAddress,
                Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
        // Get the mailbox; this happens rarely so it's ok to get it all
        Mailbox mailbox = Mailbox.restoreMailboxWithId(this, mailboxId);
        if (mailbox == null)
            return;
        int syncInterval = mailbox.mSyncInterval;
        // If we're syncable, look further...
        if (ContentResolver.getIsSyncable(acct, authority) > 0) {
            // If we're supposed to sync automatically (push), set to push if it's not
            if (ContentResolver.getSyncAutomatically(acct, authority)) {
                if (syncInterval == Mailbox.CHECK_INTERVAL_NEVER || syncInterval > 0) {
                    log("Sync for " + mailbox.mDisplayName + " in " + acct.name + ": push");
                    cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_PUSH);
                }
                // If we're NOT supposed to push, and we're not set up that way, change it
            } else if (syncInterval != Mailbox.CHECK_INTERVAL_NEVER) {
                log("Sync for " + mailbox.mDisplayName + " in " + acct.name + ": manual");
                cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_NEVER);
            }
            // If not, set it to never check
        } else if (syncInterval != Mailbox.CHECK_INTERVAL_NEVER) {
            log("Sync for " + mailbox.mDisplayName + " in " + acct.name + ": manual");
            cv.put(MailboxColumns.SYNC_INTERVAL, Mailbox.CHECK_INTERVAL_NEVER);
        }

        // If we've made a change, update the Mailbox, and kick
        if (cv.containsKey(MailboxColumns.SYNC_INTERVAL)) {
            mResolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId), cv, null, null);
            kick("sync settings change");
        }
    }
}

From source file:com.zns.comicdroid.activity.Edit.java

private void UpdateComics() {
    final ContentValues values = new ContentValues();
    final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(this);
    String title = mEtTitle.getText().toString().trim();
    if (title.toLowerCase(Locale.ENGLISH).startsWith("the ")) {
        title = title.substring(4) + ", The";
    }//from   www .j  a  va 2s  .co m

    if (mComics != null && mComics.size() > 1) {
        if (!isEmpty(mEtTitle))
            values.put("Title", title);
        if (!isEmpty(mEtSubtitle))
            values.put("SubTitle", mEtSubtitle.getText().toString());
        if (!isEmpty(mEtAuthor))
            values.put("Author", mEtAuthor.getText().toString());
        if (!isEmpty(mEtIllustrator))
            values.put("Illustrator", mEtIllustrator.getText().toString());
        if (!isEmpty(mEtPublisher))
            values.put("Publisher", mEtPublisher.getText().toString());
        if (mSpGroup.getSelectedItemPosition() > 0) {
            Group g = (Group) mSpGroup.getSelectedItem();
            values.put("GroupId", g.getId());
        }
    } else {
        //Strings
        values.put("Title", title);
        values.put("SubTitle", mEtSubtitle.getText().toString());
        values.put("Author", mEtAuthor.getText().toString());
        values.put("Illustrator", mEtIllustrator.getText().toString());
        values.put("Publisher", mEtPublisher.getText().toString());
        values.put("Issues", mEtIssues.getText().toString());
        //Integers
        if (!isEmpty(mEtIssue)) {
            if (isValidInt(mEtIssue.getText().toString())) {
                values.put("Issue", Integer.parseInt(mEtIssue.getText().toString()));
            } else {
                Toast.makeText(this, R.string.edit_issueerror, Toast.LENGTH_LONG).show();
                return;
            }
        } else {
            values.putNull("Issue");
        }
        if (!isEmpty(mEtPageCount)) {
            if (isValidInt(mEtPageCount.getText().toString())) {
                values.put("PageCount", Integer.parseInt(mEtPageCount.getText().toString()));
            } else {
                Toast.makeText(this, R.string.edit_pagecounterror, Toast.LENGTH_LONG).show();
                return;
            }
        } else {
            values.putNull("PageCount");
        }
        //Dates
        try {
            if (!isEmpty(mEtPublished)) {

                values.put("PublishDate",
                        getDBHelper().GetDateStamp(mEtPublished.getText().toString(), dateFormat));
            } else {
                values.putNull("PublishDate");
            }
            if (!isEmpty(mEtAdded)) {
                values.put("AddedDate", getDBHelper().GetDateStamp(mEtAdded.getText().toString(), dateFormat));
            } else {
                values.putNull("AddedDate");
            }
        } catch (ParseException e) {
            Toast.makeText(this, getString(R.string.edit_dateerror) + " " + dateFormat.format(new Date()),
                    Toast.LENGTH_LONG).show();
            return;
        }
        //Image
        if (mNewImage != null) {
            values.put("ImageUrl", "");
            values.put("Image", new File(mNewImage).getName());
        }
        //Group
        if (mSpGroup.getSelectedItemPosition() > 0) {
            Group g = (Group) mSpGroup.getSelectedItem();
            values.put("GroupId", g.getId());
        } else {
            values.putNull("GroupId");
        }
    }

    if (mComics != null) {
        //UPDATE
        StringBuilder sbWhere = new StringBuilder("_id IN (");
        String[] ids = new String[mComics.size()];
        int i = 0;
        for (Comic c : mComics) {
            sbWhere.append("?,");
            ids[i] = Integer.toString(c.getId());
            i++;
        }
        sbWhere.setLength(sbWhere.length() - 1);
        sbWhere.append(")");

        getDBHelper().update("tblBooks", values, sbWhere.toString(), ids);
    } else {
        //INSERT
        if (!values.containsKey("AddedDate") || values.get("AddedDate") == null) {
            values.remove("AddedDate");
            values.put("AddedDate", (int) (System.currentTimeMillis() / 1000L));
        }
        long id = getDBHelper().insert("tblBooks", values);
        Comic comic = getDBHelper().getComic((int) id);
        if (comic != null) {
            mComics = new ArrayList<Comic>();
            mComics.add(comic);
        }
    }

    //Backup
    BackupManager m = new BackupManager(this);
    m.dataChanged();

    setResult(RESULT_OK);

    Toast.makeText(this, getResources().getString(R.string.edit_done), Toast.LENGTH_LONG).show();
}

From source file:org.opendatakit.common.android.provider.impl.FormsProviderImpl.java

@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
    List<String> segments = uri.getPathSegments();

    if (segments.size() < 1 || segments.size() > 2) {
        throw new IllegalArgumentException("Unknown URI (incorrect number of segments!) " + uri);
    }//  w ww .j  a va 2 s . co  m

    String appName = segments.get(0);
    ODKFileUtils.verifyExternalStorageAvailability();
    ODKFileUtils.assertDirectoryStructure(appName);
    WebLogger log = WebLogger.getLogger(appName);

    String uriFormId = ((segments.size() == 2) ? segments.get(1) : null);
    boolean isNumericId = StringUtils.isNumeric(uriFormId);

    // Modify the where clause to account for the presence of
    // a form id. Accept either:
    // (1) numeric _ID value
    // (2) string FORM_ID value.
    String whereId;
    String[] whereIdArgs;

    if (uriFormId == null) {
        whereId = where;
        whereIdArgs = whereArgs;
    } else {
        if (TextUtils.isEmpty(where)) {
            whereId = (isNumericId ? FormsColumns._ID : FormsColumns.FORM_ID) + "=?";
            whereIdArgs = new String[1];
            whereIdArgs[0] = uriFormId;
        } else {
            whereId = (isNumericId ? FormsColumns._ID : FormsColumns.FORM_ID) + "=? AND (" + where + ")";
            whereIdArgs = new String[whereArgs.length + 1];
            whereIdArgs[0] = uriFormId;
            for (int i = 0; i < whereArgs.length; ++i) {
                whereIdArgs[i + 1] = whereArgs[i];
            }
        }
    }

    /*
     * First, find out what records match this query, and if they refer to two
     * or more (formId,formVersion) tuples, then be sure to remove all
     * FORM_MEDIA_PATH references. Otherwise, if they are all for the same
     * tuple, and the update specifies a FORM_MEDIA_PATH, move all the
     * non-matching directories elsewhere.
     */
    Integer idValue = null;
    String tableIdValue = null;
    String formIdValue = null;
    HashMap<File, DirType> mediaDirs = new HashMap<File, DirType>();
    boolean multiset = false;
    Cursor c = null;
    try {
        c = this.query(uri, null, whereId, whereIdArgs, null);
        if (c == null) {
            throw new SQLException(
                    "FAILED Update of " + uri + " -- query for existing row did not return a cursor");
        }
        if (c.getCount() >= 1) {
            FormIdVersion ref = null;
            c.moveToPosition(-1);
            while (c.moveToNext()) {
                idValue = ODKDatabaseUtils.get().getIndexAsType(c, Integer.class,
                        c.getColumnIndex(FormsColumns._ID));
                tableIdValue = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.TABLE_ID));
                formIdValue = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.FORM_ID));
                String tableId = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.TABLE_ID));
                String formId = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.FORM_ID));
                String formVersion = ODKDatabaseUtils.get().getIndexAsString(c,
                        c.getColumnIndex(FormsColumns.FORM_VERSION));
                FormIdVersion cur = new FormIdVersion(tableId, formId, formVersion);

                int appRelativeMediaPathIdx = c.getColumnIndex(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH);
                String mediaPath = ODKDatabaseUtils.get().getIndexAsString(c, appRelativeMediaPathIdx);
                if (mediaPath != null) {
                    mediaDirs.put(ODKFileUtils.asAppFile(appName, mediaPath),
                            (tableIdValue == null) ? DirType.FRAMEWORK : DirType.FORMS);
                }

                if (ref != null && !ref.equals(cur)) {
                    multiset = true;
                    break;
                } else {
                    ref = cur;
                }
            }
        }
    } catch (Exception e) {
        log.w(t, "FAILED Update of " + uri + " -- query for existing row failed: " + e.toString());

        if (e instanceof SQLException) {
            throw (SQLException) e;
        } else {
            throw new SQLException(
                    "FAILED Update of " + uri + " -- query for existing row failed: " + e.toString());
        }
    } finally {
        if (c != null) {
            c.close();
        }
    }

    if (multiset) {
        // don't let users manually update media path
        // we are referring to two or more (formId,formVersion) tuples.
        if (values.containsKey(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH)) {
            values.remove(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH);
        }
    } else if (values.containsKey(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH)) {
        // we are not a multiset and we are setting the media path
        // try to move all the existing non-matching media paths to
        // somewhere else...
        File mediaPath = ODKFileUtils.asAppFile(appName,
                values.getAsString(FormsColumns.APP_RELATIVE_FORM_MEDIA_PATH));
        for (HashMap.Entry<File, DirType> entry : mediaDirs.entrySet()) {
            File altPath = entry.getKey();
            if (!altPath.equals(mediaPath)) {
                try {
                    moveDirectory(appName, entry.getValue(), altPath);
                } catch (IOException e) {
                    e.printStackTrace();
                    log.e(t, "Attempt to move " + altPath.getAbsolutePath() + " failed: " + e.toString());
                }
            }
        }
        // OK. we have moved the existing form definitions elsewhere. We can
        // proceed with update...
    }

    // ensure that all values are correct and ignore some user-supplied
    // values...
    patchUpValues(appName, values);

    // Make sure that the necessary fields are all set
    if (values.containsKey(FormsColumns.DATE) == true) {
        Date today = new Date();
        String ts = new SimpleDateFormat(getContext().getString(R.string.added_on_date_at_time),
                Locale.getDefault()).format(today);
        values.put(FormsColumns.DISPLAY_SUBTEXT, ts);
    }

    SQLiteDatabase db = null;
    int count;
    try {
        // OK Finally, now do the update...
        db = DatabaseFactory.get().getDatabase(getContext(), appName);
        db.beginTransaction();
        count = db.update(DatabaseConstants.FORMS_TABLE_NAME, values, whereId, whereIdArgs);
        db.setTransactionSuccessful();
    } catch (Exception e) {
        e.printStackTrace();
        log.w(t, "Unable to perform update " + uri);
        return 0;
    } finally {
        if (db != null) {
            db.endTransaction();
            db.close();
        }
    }

    if (count == 1) {
        Uri formUri = Uri.withAppendedPath(
                Uri.withAppendedPath(Uri.parse("content://" + getFormsAuthority()), appName), formIdValue);
        getContext().getContentResolver().notifyChange(formUri, null);
        Uri idUri = Uri.withAppendedPath(
                Uri.withAppendedPath(Uri.parse("content://" + getFormsAuthority()), appName),
                Long.toString(idValue));
        getContext().getContentResolver().notifyChange(idUri, null);
    } else {
        getContext().getContentResolver().notifyChange(uri, null);
    }
    return count;
}

From source file:org.opendatakit.services.database.utlities.ODKDatabaseImplUtils.java

/**
 * Inserts a checkpoint row for the given rowId in the tableId. Checkpoint
 * rows are created by ODK Survey to hold intermediate values during the
 * filling-in of the form. They act as restore points in the Survey, should
 * the application die.// ww w .  j a  v  a  2 s. c  o m
 *
 * @param db
 * @param tableId
 * @param orderedColumns
 * @param cvValues
 * @param rowId
 * @param activeUser
 * @param rolesList
 * @param locale
 */
public void insertCheckpointRowWithId(OdkConnectionInterface db, String tableId, OrderedColumns orderedColumns,
        ContentValues cvValues, String rowId, String activeUser, String rolesList, String locale)
        throws ActionNotAuthorizedException {

    if (cvValues.size() <= 0) {
        throw new IllegalArgumentException(t + ": No values to add into table for checkpoint" + tableId);
    }

    // these are all managed in the database layer...
    // the user should NOT set them...

    if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TIMESTAMP)) {
        throw new IllegalArgumentException(
                t + ": No user supplied savepoint timestamp can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TYPE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied savepoint type can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.ROW_ETAG)) {
        throw new IllegalArgumentException(t + ": No user supplied row ETag can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.SYNC_STATE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied sync state can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.CONFLICT_TYPE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied conflict type can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.FILTER_VALUE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied filter value can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.FILTER_TYPE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied filter type can be included for a checkpoint");
    }

    // If a rowId is specified, a cursor will be needed to
    // get the current row to create a checkpoint with the relevant data
    Cursor c = null;
    try {
        // Allow the user to pass in no rowId if this is the first
        // checkpoint row that the user is adding
        if (rowId == null) {

            // TODO: is this even valid any more? I think we disallow this in the AIDL flow.

            String rowIdToUse = LocalizationUtils.genUUID();
            HashMap<String, Object> currValues = new HashMap<String, Object>();
            for (String key : cvValues.keySet()) {
                currValues.put(key, cvValues.get(key));
            }
            currValues.put(DataTableColumns._ID, rowIdToUse);
            currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name());
            insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList,
                    locale, true, null, null);
            return;
        }

        StringBuilder b = new StringBuilder();
        b.append(K_DATATABLE_ID_EQUALS_PARAM).append(S_AND).append(DataTableColumns.SAVEPOINT_TIMESTAMP)
                .append(" IN (SELECT MAX(").append(DataTableColumns.SAVEPOINT_TIMESTAMP).append(") FROM ")
                .append(tableId).append(K_WHERE).append(K_DATATABLE_ID_EQUALS_PARAM).append(")");
        c = db.query(tableId, null, b.toString(), new Object[] { rowId, rowId }, null, null, null, null);
        c.moveToFirst();

        if (c.getCount() > 1) {
            throw new IllegalStateException(t + ": More than one checkpoint at a timestamp");
        }

        // Inserting a checkpoint for the first time
        if (c.getCount() <= 0) {
            HashMap<String, Object> currValues = new HashMap<String, Object>();
            for (String key : cvValues.keySet()) {
                currValues.put(key, cvValues.get(key));
            }
            currValues.put(DataTableColumns._ID, rowId);
            currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name());
            insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList,
                    locale, true, null, null);
            return;
        } else {
            // Make sure that the conflict_type of any existing row
            // is null, otherwise throw an exception
            int conflictIndex = c.getColumnIndex(DataTableColumns.CONFLICT_TYPE);
            if (!c.isNull(conflictIndex)) {
                throw new IllegalStateException(
                        t + ":  A checkpoint cannot be added for a row that is in conflict");
            }

            HashMap<String, Object> currValues = new HashMap<String, Object>();
            for (String key : cvValues.keySet()) {
                currValues.put(key, cvValues.get(key));
            }

            // This is unnecessary
            // We should only have one row at this point
            //c.moveToFirst();

            String priorFilterType = null;
            String priorFilterValue = null;

            // Get the number of columns to iterate over and add
            // those values to the content values
            for (int i = 0; i < c.getColumnCount(); i++) {
                String name = c.getColumnName(i);

                if (currValues.containsKey(name)) {
                    continue;
                }

                // omitting savepoint timestamp will generate a new timestamp.
                if (name.equals(DataTableColumns.SAVEPOINT_TIMESTAMP)) {
                    continue;
                }

                // set savepoint type to null to mark this as a checkpoint
                if (name.equals(DataTableColumns.SAVEPOINT_TYPE)) {
                    currValues.put(name, null);
                    continue;
                }

                // sync state (a non-null field) should either remain 'new_row'
                // or be set to 'changed' for all other existing values.
                if (name.equals(DataTableColumns.SYNC_STATE)) {
                    String priorState = c.getString(i);
                    if (priorState.equals(SyncState.new_row.name())) {
                        currValues.put(name, SyncState.new_row.name());
                    } else {
                        currValues.put(name, SyncState.changed.name());
                    }
                    continue;
                }

                if (c.isNull(i)) {
                    currValues.put(name, null);
                    continue;
                }

                // otherwise, just copy the values over...
                Class<?> theClass = CursorUtils.getIndexDataType(c, i);
                Object object = CursorUtils.getIndexAsType(c, theClass, i);
                insertValueIntoContentValues(currValues, theClass, name, object);

                if (name.equals(DataTableColumns.FILTER_TYPE)) {
                    priorFilterType = c.getString(i);
                }

                if (name.equals(DataTableColumns.FILTER_VALUE)) {
                    priorFilterValue = c.getString(i);
                }
            }

            insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList,
                    locale, false, priorFilterType, priorFilterValue);
        }
    } finally {
        if (c != null && !c.isClosed()) {
            c.close();
        }
    }
}

From source file:org.opendatakit.services.database.utilities.ODKDatabaseImplUtils.java

/**
 * Inserts a checkpoint row for the given rowId in the tableId. Checkpoint
 * rows are created by ODK Survey to hold intermediate values during the
 * filling-in of the form. They act as restore points in the Survey, should
 * the application die./*  w  w w .j  av  a 2  s. co m*/
 *
 * @param db
 * @param tableId
 * @param orderedColumns
 * @param cvValues
 * @param rowId
 * @param activeUser
 * @param rolesList
 * @param locale
 */
public void insertCheckpointRowWithId(OdkConnectionInterface db, String tableId, OrderedColumns orderedColumns,
        ContentValues cvValues, String rowId, String activeUser, String rolesList, String locale)
        throws ActionNotAuthorizedException {

    if (cvValues.size() <= 0) {
        throw new IllegalArgumentException(t + ": No values to add into table for checkpoint" + tableId);
    }

    // these are all managed in the database layer...
    // the user should NOT set them...

    if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TIMESTAMP)) {
        throw new IllegalArgumentException(
                t + ": No user supplied savepoint timestamp can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.SAVEPOINT_TYPE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied savepoint type can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.ROW_ETAG)) {
        throw new IllegalArgumentException(t + ": No user supplied row ETag can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.SYNC_STATE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied sync state can be included for a checkpoint");
    }

    if (cvValues.containsKey(DataTableColumns.CONFLICT_TYPE)) {
        throw new IllegalArgumentException(
                t + ": No user supplied conflict type can be included for a checkpoint");
    }

    // If a rowId is specified, a cursor will be needed to
    // get the current row to create a checkpoint with the relevant data
    Cursor c = null;
    try {
        // Allow the user to pass in no rowId if this is the first
        // checkpoint row that the user is adding
        if (rowId == null) {

            // TODO: is this even valid any more? I think we disallow this in the AIDL flow.

            String rowIdToUse = LocalizationUtils.genUUID();
            HashMap<String, Object> currValues = new HashMap<String, Object>();
            for (String key : cvValues.keySet()) {
                currValues.put(key, cvValues.get(key));
            }
            currValues.put(DataTableColumns._ID, rowIdToUse);
            currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name());
            insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList,
                    locale, true, null, null, null, null, null);
            return;
        }

        StringBuilder b = new StringBuilder();
        b.append(K_DATATABLE_ID_EQUALS_PARAM).append(S_AND).append(DataTableColumns.SAVEPOINT_TIMESTAMP)
                .append(" IN (SELECT MAX(").append(DataTableColumns.SAVEPOINT_TIMESTAMP).append(") FROM ")
                .append(tableId).append(K_WHERE).append(K_DATATABLE_ID_EQUALS_PARAM).append(")");
        c = db.query(tableId, null, b.toString(), new Object[] { rowId, rowId }, null, null, null, null);
        c.moveToFirst();

        if (c.getCount() > 1) {
            throw new IllegalStateException(t + ": More than one checkpoint at a timestamp");
        }

        // Inserting a checkpoint for the first time
        if (c.getCount() <= 0) {
            HashMap<String, Object> currValues = new HashMap<String, Object>();
            for (String key : cvValues.keySet()) {
                currValues.put(key, cvValues.get(key));
            }
            currValues.put(DataTableColumns._ID, rowId);
            currValues.put(DataTableColumns.SYNC_STATE, SyncState.new_row.name());
            insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList,
                    locale, true, null, null, null, null, null);
            return;
        } else {
            // Make sure that the conflict_type of any existing row
            // is null, otherwise throw an exception
            int conflictIndex = c.getColumnIndex(DataTableColumns.CONFLICT_TYPE);
            if (!c.isNull(conflictIndex)) {
                throw new IllegalStateException(
                        t + ":  A checkpoint cannot be added for a row that is in conflict");
            }

            // these are all managed in the database layer...
            // the user should NOT set them...

            if (cvValues.containsKey(DataTableColumns.DEFAULT_ACCESS)) {
                throw new IllegalArgumentException(
                        t + ": No user supplied default access can be included for a checkpoint");
            }

            if (cvValues.containsKey(DataTableColumns.ROW_OWNER)) {
                throw new IllegalArgumentException(
                        t + ": No user supplied row owner can be included for a checkpoint");
            }

            if (cvValues.containsKey(DataTableColumns.GROUP_READ_ONLY)) {
                throw new IllegalArgumentException(
                        t + ": No user supplied group read only can be included for a checkpoint");
            }

            if (cvValues.containsKey(DataTableColumns.GROUP_MODIFY)) {
                throw new IllegalArgumentException(
                        t + ": No user supplied group modify can be included for a checkpoint");
            }

            if (cvValues.containsKey(DataTableColumns.GROUP_PRIVILEGED)) {
                throw new IllegalArgumentException(
                        t + ": No user supplied group privileged can be included for a checkpoint");
            }

            HashMap<String, Object> currValues = new HashMap<String, Object>();
            for (String key : cvValues.keySet()) {
                currValues.put(key, cvValues.get(key));
            }

            // This is unnecessary
            // We should only have one row at this point
            //c.moveToFirst();

            String priorDefaultAccess = null;
            String priorOwner = null;
            String priorGroupReadOnly = null;
            String priorGroupModify = null;
            String priorGroupPrivileged = null;

            // Get the number of columns to iterate over and add
            // those values to the content values
            for (int i = 0; i < c.getColumnCount(); i++) {
                String name = c.getColumnName(i);

                if (name.equals(DataTableColumns.DEFAULT_ACCESS)) {
                    priorDefaultAccess = c.getString(i);
                }

                if (name.equals(DataTableColumns.ROW_OWNER)) {
                    priorOwner = c.getString(i);
                }

                if (name.equals(DataTableColumns.GROUP_READ_ONLY)) {
                    priorGroupReadOnly = c.getString(i);
                }

                if (name.equals(DataTableColumns.GROUP_MODIFY)) {
                    priorGroupModify = c.getString(i);
                }

                if (name.equals(DataTableColumns.GROUP_PRIVILEGED)) {
                    priorGroupPrivileged = c.getString(i);
                }

                if (currValues.containsKey(name)) {
                    continue;
                }

                // omitting savepoint timestamp will generate a new timestamp.
                if (name.equals(DataTableColumns.SAVEPOINT_TIMESTAMP)) {
                    continue;
                }

                // set savepoint type to null to mark this as a checkpoint
                if (name.equals(DataTableColumns.SAVEPOINT_TYPE)) {
                    currValues.put(name, null);
                    continue;
                }

                // sync state (a non-null field) should either remain 'new_row'
                // or be set to 'changed' for all other existing values.
                if (name.equals(DataTableColumns.SYNC_STATE)) {
                    String priorState = c.getString(i);
                    if (priorState.equals(SyncState.new_row.name())) {
                        currValues.put(name, SyncState.new_row.name());
                    } else {
                        currValues.put(name, SyncState.changed.name());
                    }
                    continue;
                }

                if (c.isNull(i)) {
                    currValues.put(name, null);
                    continue;
                }

                // otherwise, just copy the values over...
                Class<?> theClass = CursorUtils.getIndexDataType(c, i);
                Object object = CursorUtils.getIndexAsType(c, theClass, i);
                insertValueIntoContentValues(currValues, theClass, name, object);
            }

            insertCheckpointIntoExistingTable(db, tableId, orderedColumns, currValues, activeUser, rolesList,
                    locale, false, priorDefaultAccess, priorOwner, priorGroupReadOnly, priorGroupModify,
                    priorGroupPrivileged);
        }
    } finally {
        if (c != null && !c.isClosed()) {
            c.close();
        }
    }
}

From source file:org.opendatakit.services.database.utlities.ODKDatabaseImplUtils.java

/**
 * Delete any prior server conflict row.
 * Examine database and incoming server values to determine how to apply the
 * server values to the database. This might delete the existing row, update
 * it, or create a conflict row.//  w w w  .  j a  va 2  s. c o  m
 *
 * @param db
 * @param tableId
 * @param orderedColumns
 * @param serverValues  field values for this row coming from server.
 *                      All fields must have values. The SyncState field
 *                      (a local field that does not come from the server)
 *                      should be "changed" or "deleted"
 * @param rowId
 * @param activeUser
 * @param rolesList  passed in to determine if the current user is a privileged user
 * @param locale
 */
public void privilegedPerhapsPlaceRowIntoConflictWithId(OdkConnectionInterface db, String tableId,
        OrderedColumns orderedColumns, ContentValues serverValues, String rowId, String activeUser,
        String rolesList, String locale) {

    AccessContext accessContext = getAccessContext(db, tableId, activeUser, rolesList);

    // The rolesList of the activeUser does not impact the execution of this action.
    boolean dbWithinTransaction = db.inTransaction();
    try {
        if (!dbWithinTransaction) {
            db.beginTransactionNonExclusive();
        }

        // delete any existing server conflict row
        this.deleteServerConflictRowWithId(db, tableId, rowId);
        // fetch the current local (possibly-in-conflict) row
        BaseTable baseTable = this.privilegedGetRowsWithId(db, tableId, rowId, activeUser);
        if (baseTable.getNumberOfRows() == 0) {
            throw new IllegalArgumentException("no matching row found for server conflict");
        } else if (baseTable.getNumberOfRows() != 1) {
            throw new IllegalArgumentException("row has checkpoints or database is corrupt");
        }

        Row localRow = baseTable.getRowAtIndex(0);

        if (localRow.getDataByKey(DataTableColumns.SAVEPOINT_TYPE) == null) {
            throw new IllegalArgumentException("row has checkpoints");
        }

        String strSyncState = localRow.getDataByKey(DataTableColumns.SYNC_STATE);
        SyncState state = SyncState.valueOf(strSyncState);

        int localRowConflictTypeBeforeSync = -1;
        if (state == SyncState.in_conflict) {
            // we need to remove the in_conflict records that refer to the
            // prior state of the server
            String localRowConflictTypeBeforeSyncStr = localRow.getDataByKey(DataTableColumns.CONFLICT_TYPE);
            if (localRowConflictTypeBeforeSyncStr == null) {
                // this row is in conflict. It MUST have a non-null conflict type.
                throw new IllegalStateException("conflict type is null on an in-conflict row");
            }

            localRowConflictTypeBeforeSync = Integer.parseInt(localRowConflictTypeBeforeSyncStr);
            if (localRowConflictTypeBeforeSync == ConflictType.SERVER_DELETED_OLD_VALUES
                    || localRowConflictTypeBeforeSync == ConflictType.SERVER_UPDATED_UPDATED_VALUES) {
                // should be impossible
                throw new IllegalStateException("only the local conflict record should remain");
            }
        }

        boolean isServerRowDeleted = serverValues.getAsString(DataTableColumns.SYNC_STATE)
                .equals(SyncState.deleted.name());

        boolean executeDropThrough = false;

        if (isServerRowDeleted) {
            if (state == SyncState.synced) {
                // the server's change should be applied locally.
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else if (state == SyncState.synced_pending_files) {
                // sync logic may want to UPLOAD local files up to server before calling this routine.
                // this would prevent loss of attachments that have not yet been pushed to server.

                // the server's change should be applied locally.
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else if ((state == SyncState.deleted) || ((state == SyncState.in_conflict)
                    && (localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES))) {
                // this occurs if
                // (1) a delete request was never ACKed but it was performed
                // on the server.
                // (2) if there is an unresolved conflict held locally with the
                // local action being to delete the record, and the prior server
                // state being a value change, but the newly sync'd state now
                // reflects a deletion by another party.
                //
                // the server's change should be applied locally.
                this.privilegedDeleteRowWithId(db, tableId, rowId, activeUser);
            } else {
                // need to resolve a conflict situation...
                executeDropThrough = true;
            }
        } else if (state == SyncState.synced || state == SyncState.synced_pending_files) {
            // When a prior sync ends with conflicts, we will not update the table's "lastDataETag"
            // and when we next sync, we will pull the same server row updates as when the
            // conflicts were raised (elsewhere in the table).
            //
            // Therefore, we can expect many server row updates to have already been locally
            // applied (for the rows were not in conflict). Detect and ignore these already-
            // processed changes by testing for the server and device having identical field values.
            //

            boolean isDifferent = false;
            for (int i = 0; i < baseTable.getWidth(); ++i) {
                String colName = baseTable.getElementKey(i);
                if (DataTableColumns.ID.equals(colName) || DataTableColumns.CONFLICT_TYPE.equals(colName)
                        || DataTableColumns.EFFECTIVE_ACCESS.equals(colName)
                        || DataTableColumns.SYNC_STATE.equals(colName)) {
                    // these values are ignored during comparisons
                    continue;
                }
                String localValue = localRow.getDataByKey(colName);
                String serverValue = serverValues.containsKey(colName) ? serverValues.getAsString(colName)
                        : null;

                ElementDataType dt = ElementDataType.string;
                try {
                    ColumnDefinition cd = orderedColumns.find(colName);
                    dt = cd.getType().getDataType();
                } catch (IllegalArgumentException e) {
                    // ignore
                }
                if (!identicalValue(localValue, serverValue, dt)) {
                    isDifferent = true;
                    break;
                }
            }

            if (isDifferent) {
                // Local row needs to be updated with server values.
                //
                // detect and handle file attachment column changes and sync state
                // (need to change serverRow value of this)
                boolean hasNonNullAttachments = false;

                for (ColumnDefinition cd : orderedColumns.getColumnDefinitions()) {
                    // todo: does not handle array containing (types containing) rowpath elements
                    if (cd.isUnitOfRetention() && cd.getType().getDataType().equals(ElementDataType.rowpath)) {
                        String uriFragment = serverValues.getAsString(cd.getElementKey());
                        String localUriFragment = localRow.getDataByKey(cd.getElementKey());
                        if (uriFragment != null) {
                            if (localUriFragment == null || !localUriFragment.equals(uriFragment)) {
                                hasNonNullAttachments = true;
                                break;
                            }
                        }
                    }
                }

                // update the row from the changes on the server
                ContentValues values = new ContentValues(serverValues);

                values.put(DataTableColumns.SYNC_STATE,
                        (hasNonNullAttachments || (state == SyncState.synced_pending_files))
                                ? SyncState.synced_pending_files.name()
                                : SyncState.synced.name());
                values.putNull(DataTableColumns.CONFLICT_TYPE);

                this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser, locale,
                        false);
            }

            // and don't execute the drop-through in this case.
        } else {
            executeDropThrough = true;
        }

        if (executeDropThrough) {
            // SyncState.deleted and server is not deleting
            // SyncState.new_row and record exists on server
            // SyncState.changed and new change (or delete) on server
            // SyncState.in_conflict and new change (or delete) on server

            // ALSO: this case can occur when our prior sync attempt pulled down changes that
            // placed local row(s) in conflict -- which we have since resolved -- and we are now
            // issuing a sync to push those changes up to the server.
            //
            // This is because we do not update our local table's "lastDataETag" until we have
            // sync'd and applied all changes from the server and have no local conflicts or
            // checkpoints on the table. Because of this, when we issue a sync after resolving
            // a conflict, we will get the set of server row changes that include the row(s)
            // that were previously in conflict. This will appear as one of:
            //    changed | changed
            //    changed | deleted
            //    deleted | changed
            //    deleted | deleted
            //
            // BUT, this time, however, the local records will have the same rowETag as the
            // server record, indicating that they are valid changes (or deletions) on top of
            // the server's current version of this same row.
            //
            // If this is the case (the rowETags match), then we should not place the row into
            // conflict, but should instead ignore the reported content from the server and push
            // the local row's change or delete up to the server in the next section.
            //
            // If not, when we reach this point in the code, the rowETag of the server row
            // should **not match** our local row -- indicating that the server has a change from
            // another source, and that we should place the row into conflict.
            //
            String localRowETag = localRow.getDataByKey(DataTableColumns.ROW_ETAG);
            String serverRowETag = serverValues.getAsString(DataTableColumns.ROW_ETAG);
            boolean isDifferentRowETag = (localRowETag == null) || !localRowETag.equals(serverRowETag);
            if (!isDifferentRowETag) {
                // ignore the server record.
                // This is an update we will push to the server.
                // todo: make sure local row is not in_conflict at this point.
                if (state == SyncState.in_conflict) {
                    // don't think this is logically possible at this point, but we should
                    // transition record to changed or deleted state.
                    ContentValues values = new ContentValues();
                    for (int i = 0; i < baseTable.getWidth(); ++i) {
                        String colName = baseTable.getElementKey(i);
                        if (DataTableColumns.EFFECTIVE_ACCESS.equals(colName)) {
                            continue;
                        }
                        if (localRow.getDataByIndex(i) == null) {
                            values.putNull(colName);
                        } else {
                            values.put(colName, localRow.getDataByIndex(i));
                        }
                    }
                    values.put(DataTableColumns.ID, rowId);
                    values.putNull(DataTableColumns.CONFLICT_TYPE);
                    values.put(DataTableColumns.SYNC_STATE,
                            (localRowConflictTypeBeforeSync == ConflictType.LOCAL_DELETED_OLD_VALUES)
                                    ? SyncState.deleted.name()
                                    : SyncState.changed.name());

                    this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser,
                            locale, false);
                }
            } else {
                // figure out what the localRow conflict type should be...
                int localRowConflictType;
                if (state == SyncState.changed) {
                    // SyncState.changed and new change on server
                    localRowConflictType = ConflictType.LOCAL_UPDATED_UPDATED_VALUES;
                } else if (state == SyncState.new_row) {
                    // SyncState.new_row and record exists on server
                    // The 'new_row' case occurs if an insert is never ACKed but
                    // completes successfully on the server.
                    localRowConflictType = ConflictType.LOCAL_UPDATED_UPDATED_VALUES;
                } else if (state == SyncState.deleted) {
                    // SyncState.deleted and server is not deleting
                    localRowConflictType = ConflictType.LOCAL_DELETED_OLD_VALUES;
                } else if (state == SyncState.in_conflict) {
                    // SyncState.in_conflict and new change on server
                    // leave the local conflict type unchanged (retrieve it and
                    // use it).
                    localRowConflictType = localRowConflictTypeBeforeSync;
                } else {
                    throw new IllegalStateException("Unexpected state encountered");
                }

                boolean isDifferentPrivilegedFields = false;
                {
                    String[] privilegedColumns = new String[] { DataTableColumns.FILTER_TYPE,
                            DataTableColumns.FILTER_VALUE };
                    for (int i = 0; i < privilegedColumns.length; ++i) {
                        String colName = privilegedColumns[i];
                        String localValue = localRow.getDataByKey(colName);
                        String serverValue = serverValues.containsKey(colName)
                                ? serverValues.getAsString(colName)
                                : null;

                        ElementDataType dt = ElementDataType.string;
                        try {
                            ColumnDefinition cd = orderedColumns.find(colName);
                            dt = cd.getType().getDataType();
                        } catch (IllegalArgumentException e) {
                            // ignore
                        }

                        boolean sameValue = identicalValue(localValue, serverValue, dt);

                        if (!sameValue) {
                            isDifferentPrivilegedFields = true;
                            break;
                        }
                    }
                }
                boolean isDifferentExcludingPrivilegedFields = false;
                for (int i = 0; i < baseTable.getWidth(); ++i) {
                    String colName = baseTable.getElementKey(i);
                    if (DataTableColumns.ID.equals(colName) || DataTableColumns.CONFLICT_TYPE.equals(colName)
                            || DataTableColumns.EFFECTIVE_ACCESS.equals(colName)
                            || DataTableColumns.SYNC_STATE.equals(colName)
                            || DataTableColumns.ROW_ETAG.equals(colName)
                            || DataTableColumns.FILTER_TYPE.equals(colName)
                            || DataTableColumns.FILTER_VALUE.equals(colName)) {
                        // these values are ignored during this comparison
                        continue;
                    }
                    String localValue = localRow.getDataByKey(colName);
                    String serverValue = serverValues.containsKey(colName) ? serverValues.getAsString(colName)
                            : null;

                    ElementDataType dt = ElementDataType.string;
                    try {
                        ColumnDefinition cd = orderedColumns.find(colName);
                        dt = cd.getType().getDataType();
                    } catch (IllegalArgumentException e) {
                        // ignore
                    }

                    if (serverValue != null && dt == ElementDataType.bool) {
                        serverValue = Integer
                                .toString(DataHelper.boolToInt(serverValues.getAsBoolean(colName)));
                    }
                    boolean sameValue = identicalValue(localValue, serverValue, dt);

                    if (!sameValue) {
                        isDifferentExcludingPrivilegedFields = true;
                        break;
                    }
                }

                boolean hasNonNullDifferingServerAttachments = false;

                if (isDifferentExcludingPrivilegedFields || (state != SyncState.synced)) {
                    for (ColumnDefinition cd : orderedColumns.getColumnDefinitions()) {
                        // todo: does not handle array containing (types containing) rowpath elements
                        if (cd.isUnitOfRetention()
                                && cd.getType().getDataType().equals(ElementDataType.rowpath)) {
                            String uriFragment = serverValues.getAsString(cd.getElementKey());
                            String localUriFragment = localRow.getDataByKey(cd.getElementKey());
                            if (uriFragment != null) {
                                if (localUriFragment == null || !localUriFragment.equals(uriFragment)) {
                                    hasNonNullDifferingServerAttachments = true;
                                }
                            }
                        }
                    }
                }

                ContentValues values = new ContentValues(serverValues);

                if (isDifferentExcludingPrivilegedFields || isDifferentPrivilegedFields || isDifferentRowETag
                        || isServerRowDeleted) {

                    if (isDifferentExcludingPrivilegedFields || isServerRowDeleted
                            || (localRowConflictType == ConflictType.LOCAL_DELETED_OLD_VALUES)
                            || (accessContext.isPrivilegedUser && isDifferentPrivilegedFields)) {

                        this.placeRowIntoConflict(db, tableId, rowId, localRowConflictType);
                        serverValues.put(DataTableColumns.SYNC_STATE, SyncState.in_conflict.name());
                        serverValues.put(DataTableColumns.CONFLICT_TYPE,
                                (isServerRowDeleted ? ConflictType.SERVER_DELETED_OLD_VALUES
                                        : ConflictType.SERVER_UPDATED_UPDATED_VALUES));
                        this.privilegedInsertRowWithId(db, tableId, orderedColumns, serverValues, rowId,
                                activeUser, locale, false);
                    } else {
                        // just apply the server RowETag and filterScope to the local row
                        values.put(DataTableColumns.SYNC_STATE,
                                hasNonNullDifferingServerAttachments ? SyncState.synced_pending_files.name()
                                        : SyncState.synced.name());
                        values.putNull(DataTableColumns.CONFLICT_TYPE);

                        // move the local conflict back into the normal non-conflict (null) state
                        // set the sync state to "changed" temporarily (otherwise we can't update)

                        this.restoreRowFromConflict(db, tableId, rowId, SyncState.changed,
                                localRowConflictTypeBeforeSync);

                        this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser,
                                locale, false);
                    }
                } else {
                    // data matches -- update row to adjust sync state and conflict type
                    // if needed.
                    SyncState destState = (hasNonNullDifferingServerAttachments
                            || (state == SyncState.synced_pending_files)) ? SyncState.synced_pending_files
                                    : SyncState.synced;

                    if ((state != destState) || isDifferentRowETag
                            || (localRow.getDataByKey(DataTableColumns.CONFLICT_TYPE) != null)) {
                        // todo: handle case where local row was in conflict
                        // server has now matched the local row's state. i.e.,
                        // update rowEtag, clear conflictType and adjust syncState on row.
                        values.put(DataTableColumns.SYNC_STATE, destState.name());
                        values.putNull(DataTableColumns.CONFLICT_TYPE);

                        // move the local conflict back into the normal non-conflict (null) state
                        // set the sync state to "changed" temporarily (otherwise we can't update)

                        this.restoreRowFromConflict(db, tableId, rowId, SyncState.changed,
                                localRowConflictTypeBeforeSync);

                        this.privilegedUpdateRowWithId(db, tableId, orderedColumns, values, rowId, activeUser,
                                locale, false);

                        WebLogger.getLogger(db.getAppName()).w(t,
                                "identical rows returned from server -- " + "SHOULDN'T THESE NOT HAPPEN?");
                    }
                }
            }
        }

        if (!dbWithinTransaction) {
            db.setTransactionSuccessful();
        }
    } finally {
        if (!dbWithinTransaction) {
            db.endTransaction();
        }
    }
}