private void synchronize(ChangeRecord record) throws IOException {
    try {
      switch (record.getOperation()) {
        case LOCAL_INSERT:
          {
            // Get remote id for the parent folder
            File local = DriveUtils.absolutePath(record.getLocalFile());
            String remoteParent =
                stub.storage().localToRemote().get(DriveUtils.relativePath(local.getParentFile()));
            if (!StringUtils.isEmpty(remoteParent)
                && !stub.storage().localToRemote().containsKey(record.getLocalFile())) {
              // Ignore insert request that doesn't have a parent or
              // already have a mapping.
              // The insert operation will be accomplished by the topmost
              // folder
              String remoteId = stub.transmit().upload(remoteParent, local);
              record.setRemoteFileId(remoteId);
            }
            break;
          }
        case LOCAL_DELETE:
          {
            if (!StringUtils.isEmpty(record.getRemoteFileId())) {
              stub.transmit().delete(record.getRemoteFileId());
            } else {
              logger.warn(
                  "Local deletion has no remote reference, make sure the storage is correct.");
            }
            break;
          }
        case LOCAL_CHANGE:
          {
            if (!StringUtils.isEmpty(record.getRemoteFileId())) {
              File local = DriveUtils.absolutePath(record.getLocalFile());
              stub.transmit().update(record.getRemoteFileId(), local);
            } else {
              logger.warn(
                  "Local change has no remote reference, make sure the storage is correct.");
              logger.warn("Trying to insert the new record");
              // Modify it to a local insert
              record.setOperation(Operation.LOCAL_INSERT);
              synchronize(record);
            }
            break;
          }
        case LOCAL_RENAME:
          {
            if (!StringUtils.isEmpty(record.getRemoteFileId())) {
              stub.transmit().rename(record.getRemoteFileId(), (String) record.getContext()[0]);
            } else {
              logger.warn(
                  "Local rename has no remote reference, make sure the storage is correct.");
              logger.warn("Trying to insert the new record");
              // Modify it to be a local insert
              record.setOperation(Operation.LOCAL_INSERT);
              synchronize(record);
            }
            break;
          }
        case REMOTE_INSERT:
          {
            if (stub.storage().remoteToLocal().containsKey(record.getRemoteFileId())) {
              // This file/folder already had been created
              break;
            }
            com.google.api.services.drive.model.File file = record.getContext(0);

            // Depth search of parent that has a local root
            List<ParentReference> path = new ArrayList<ParentReference>();
            File local = pathToLocal(record.getRemoteFileId(), path);
            if (null == local) {
              // Check whether this file is trashed
              if (file.getLabels().getTrashed()) {
                return;
              }
              if (file.getShared()) {
                return;
              } else {
                throw new IllegalArgumentException("Non-trash file has no known parent");
              }
            }
            File parent = local;
            for (ParentReference node : path) {
              stub.transmit().download(node.getId(), parent);
              parent = DriveUtils.absolutePath(stub.storage().remoteToLocal().get(node.getId()));
            }
            stub.transmit().download(record.getRemoteFileId(), parent);
            record.setLocalFile(stub.storage().remoteToLocal().get(record.getRemoteFileId()));
            if (StringUtils.isEmpty(record.getLocalFile())) {
              break;
            }
            break;
          }
        case REMOTE_DELETE:
          {
            if (StringUtils.isEmpty(record.getLocalFile())) {
              // No local file, remote file should be a trashed one.
              if (logger.isDebugEnabled()) {
                logger.debug(
                    "No corresponding local file for deletion. Remote file may be trashed");
              }
              break;
            }
            File localFile = DriveUtils.absolutePath(record.getLocalFile());
            String localParent = DriveUtils.relativePath(localFile.getParentFile());
            // Preserve the original context
            record.setContext(new Object[] {record.getContext(0), localParent});
            deleteLocalFile(localFile);
            if (localFile.exists()) {
              if (logger.isDebugEnabled()) {
                logger.debug(
                    MessageFormat.format(
                        "Failed to delete file {0}. File may have been deleted.",
                        record.getLocalFile()));
              }
            }
            stub.storage().localToRemote().remove(record.getLocalFile());
            stub.storage().remoteToLocal().remove(record.getRemoteFileId());
            break;
          }
        case REMOTE_CHANGE:
          {
            File local = DriveUtils.absolutePath(record.getLocalFile());
            File localParent = local.getParentFile();
            com.google.api.services.drive.model.File remote = record.getContext(0);

            if (!remote.getTitle().equals(local.getName())) {
              local.delete();
              stub.storage().localToRemote().remove(record.getLocalFile());
              stub.storage().remoteToLocal().remove(record.getRemoteFileId());
            }
            stub.transmit().download(record.getRemoteFileId(), localParent);
            break;
          }
        case REMOTE_RENAME:
          {
            File local = DriveUtils.absolutePath(record.getLocalFile());
            com.google.api.services.drive.model.File remote = record.getContext(0);
            String remoteName = remote.getTitle();
            File newName =
                new File(local.getParentFile().getAbsolutePath() + File.separator + remoteName);
            String relNewName = DriveUtils.relativePath(newName);
            record.setContext(new Object[] {record.getContext()[0], relNewName});
            local.renameTo(newName);
            stub.storage()
                .remoteToLocal()
                .put(record.getRemoteFileId(), DriveUtils.relativePath(newName));
            stub.storage().localToRemote().remove(DriveUtils.relativePath(local));
            stub.storage()
                .localToRemote()
                .put(DriveUtils.relativePath(newName), record.getRemoteFileId());
            break;
          }
      }
    } catch (Exception e) {
      logger.warn(MessageFormat.format("Exception on Change {0}. Retry later", record), e);
      FailedRecord fr = new FailedRecord(record);
      if (e instanceof GoogleJsonResponseException) {
        GoogleJsonResponseException gjre = (GoogleJsonResponseException) e;
        fr.setError(String.valueOf(gjre.getDetails().getCode()));
      } else {
        fr.setError(e.getMessage());
      }
      stub.storage().failedLog().add(fr);
    }
  }
  private void syncNotes(final String syncAccountName, final String accessToken) {
    final Drive drive = getDriveService(syncAccountName, accessToken);
    ContentResolver cr = getContentResolver();
    try {
      // loop over saved files and add any new notes to drive
      Cursor savedNotes =
          cr.query(NotesProvider.CONTENT_URI, NotesSyncQuery.PROJECTION, null, null, null);
      if (savedNotes.moveToFirst()) {
        do {
          final String driveId = savedNotes.getString(NotesSyncQuery.DRIVE_ID);
          if (TextUtils.isEmpty(driveId)) {
            // exists locally but not in drive Ð upload it
            File newNote = new File();
            newNote.setTitle(savedNotes.getString(NotesSyncQuery.TITLE));
            newNote.setMimeType(NOTE_MIME_TYPE);

            File inserted =
                drive
                    .files()
                    .insert(
                        newNote,
                        ByteArrayContent.fromString(
                            NOTE_MIME_TYPE, savedNotes.getString(NotesSyncQuery.BODY)))
                    .execute();

            // save the drive id to the db
            ContentValues cv = new ContentValues();
            cv.put(NotesProvider.KEY_DRIVE_ID, inserted.getId());
            Uri noteUri =
                ContentUris.withAppendedId(
                    NotesProvider.CONTENT_URI, savedNotes.getLong(NotesSyncQuery.ID));
            cr.update(noteUri, cv, null, null);
          } else {
            // TODO compare timestamps etc.
          }
        } while (savedNotes.moveToNext());
      }

      // loop over all files in drive and see if any need to be
      // synced locally
      FileList driveFilesList = drive.files().list().execute();
      for (File remote : driveFilesList.getItems()) {
        if (remote.getLabels().getTrashed()) {
          // skip deleted files
          continue;
        }
        final String where = NotesProvider.KEY_DRIVE_ID + "=?";
        final String[] arguments = new String[] {remote.getId()};
        Cursor c =
            cr.query(
                NotesProvider.CONTENT_URI, NotesDownloadQuery.PROJECTION, where, arguments, null);
        if (c.getCount() == 0) {
          // exists in drive but not locally Ð download it
          final String title = remote.getTitle();
          final String body = getFileContents(drive, remote.getDownloadUrl());
          final ContentValues cv = new ContentValues();
          cv.put(NotesProvider.KEY_TITLE, title);
          cv.put(NotesProvider.KEY_BODY, body);
          cv.put(NotesProvider.KEY_DRIVE_ID, remote.getId());
          cv.put(NotesProvider.KEY_LAST_MODIFIED, remote.getModifiedDate().getValue());
          cr.insert(NotesProvider.CONTENT_URI, cv);
        } else {
          // TODO compare timestamps etc.
        }
      }

    } catch (IOException e) {
      // FIXME error handling
      Log.e(getClass().getSimpleName(), "Drive esplode", e);
    }
  }