@Override
 public boolean onKeyDown(int keyCode, KeyEvent event) {
   switch (keyCode) {
     case KeyEvent.KEYCODE_BACK:
       Collect.getInstance()
           .getActivityLogger()
           .logInstanceAction(this, "onKeyDown.KEYCODE_BACK", "quit");
       createQuitDrawDialog();
       return true;
     case KeyEvent.KEYCODE_DPAD_RIGHT:
       if (event.isAltPressed()) {
         Collect.getInstance()
             .getActivityLogger()
             .logInstanceAction(this, "onKeyDown.KEYCODE_DPAD_RIGHT", "showNext");
         createQuitDrawDialog();
         return true;
       }
       break;
     case KeyEvent.KEYCODE_DPAD_LEFT:
       if (event.isAltPressed()) {
         Collect.getInstance()
             .getActivityLogger()
             .logInstanceAction(this, "onKeyDown.KEYCODE_DPAD_LEFT", "showPrevious");
         createQuitDrawDialog();
         return true;
       }
       break;
   }
   return super.onKeyDown(keyCode, event);
 }
  /**
   * @param selection
   * @param selectionArgs
   * @param token
   */
  protected void uploadInstances(String selection, String[] selectionArgs, String token) {

    Cursor c = null;
    try {
      c =
          Collect.getInstance()
              .getContentResolver()
              .query(InstanceColumns.CONTENT_URI, null, selection, selectionArgs, null);

      if (c.getCount() > 0) {
        c.moveToPosition(-1);
        while (c.moveToNext()) {
          if (isCancelled()) {
            return;
          }
          String instance = c.getString(c.getColumnIndex(InstanceColumns.INSTANCE_FILE_PATH));
          String id = c.getString(c.getColumnIndex(InstanceColumns._ID));
          String jrformid = c.getString(c.getColumnIndex(InstanceColumns.JR_FORM_ID));
          Uri toUpdate = Uri.withAppendedPath(InstanceColumns.CONTENT_URI, id);
          ContentValues cv = new ContentValues();

          String formSelection = FormsColumns.JR_FORM_ID + "=?";
          String[] formSelectionArgs = {jrformid};
          Cursor formcursor =
              Collect.getInstance()
                  .getContentResolver()
                  .query(FormsColumns.CONTENT_URI, null, formSelection, formSelectionArgs, null);
          String md5 = null;
          String formFilePath = null;
          if (formcursor.getCount() > 0) {
            formcursor.moveToFirst();
            md5 = formcursor.getString(formcursor.getColumnIndex(FormsColumns.MD5_HASH));
            formFilePath =
                formcursor.getString(formcursor.getColumnIndex(FormsColumns.FORM_FILE_PATH));
          }

          if (md5 == null) {
            // fail and exit
            Log.e(tag, "no md5");
            return;
          }

          publishProgress(c.getPosition() + 1, c.getCount());
          if (!uploadOneSubmission(id, instance, jrformid, token, formFilePath)) {
            cv.put(InstanceColumns.STATUS, InstanceProviderAPI.STATUS_SUBMISSION_FAILED);
            Collect.getInstance().getContentResolver().update(toUpdate, cv, null, null);
            return;
          } else {
            cv.put(InstanceColumns.STATUS, InstanceProviderAPI.STATUS_SUBMITTED);
            Collect.getInstance().getContentResolver().update(toUpdate, cv, null, null);
          }
        }
      }
    } finally {
      if (c != null) {
        c.close();
      }
    }
  }
  /** Stores the path of selected form and finishes. */
  @Override
  protected void onListItemClick(ListView listView, View view, int position, long id) {
    // get uri to form
    long idFormsTable = ((SimpleCursorAdapter) getListAdapter()).getItemId(position);
    Uri formUri = ContentUris.withAppendedId(FormsColumns.CONTENT_URI, idFormsTable);

    Collect.getInstance()
        .getActivityLogger()
        .logAction(this, "onListItemClick", formUri.toString());

    String action = getIntent().getAction();
    if (Intent.ACTION_PICK.equals(action)) {
      // caller is waiting on a picked form
      setResult(RESULT_OK, new Intent().setData(formUri));
    } else {
      // caller wants to view/edit a form, so launch formentryactivity.
      // Use explicit intent to avoid being picked up by ODK
      startActivity(
          new Intent(getApplicationContext(), FormEntryActivity.class)
              .setData(formUri)
              .setAction(Intent.ACTION_EDIT));
    }

    finish();
  }
  /**
   * Creates a dialog with the given message. Will exit the activity when the user preses "ok" if
   * shouldExit is set to true.
   *
   * @param errorMsg
   * @param shouldExit
   */
  private void createErrorDialog(String errorMsg, final boolean shouldExit) {

    Collect.getInstance().getActivityLogger().logAction(this, "createErrorDialog", "show");

    mAlertDialog = new AlertDialog.Builder(this).create();
    mAlertDialog.setIcon(android.R.drawable.ic_dialog_info);
    mAlertDialog.setMessage(errorMsg);
    DialogInterface.OnClickListener errorListener =
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int i) {
            switch (i) {
              case DialogInterface.BUTTON_POSITIVE:
                Collect.getInstance()
                    .getActivityLogger()
                    .logAction(this, "createErrorDialog", shouldExit ? "exitApplication" : "OK");
                if (shouldExit) {
                  finish();
                }
                break;
            }
          }
        };
    mAlertDialog.setCancelable(false);
    mAlertDialog.setButton(getString(R.string.ok), errorListener);
    mAlertDialog.show();
  }
  private void createAlertDialog(String message) {
    Collect.getInstance().getActivityLogger().logAction(this, "createAlertDialog", "show");

    mAlertDialog = new AlertDialog.Builder(this).create();
    mAlertDialog.setTitle(getString(R.string.upload_results));
    mAlertDialog.setMessage(message);
    DialogInterface.OnClickListener quitListener =
        new DialogInterface.OnClickListener() {
          @Override
          public void onClick(DialogInterface dialog, int i) {
            switch (i) {
              case DialogInterface.BUTTON_POSITIVE: // ok
                Collect.getInstance()
                    .getActivityLogger()
                    .logAction(this, "createAlertDialog", "OK");
                // always exit this activity since it has no interface
                mAlertShowing = false;
                finish();
                break;
            }
          }
        };
    mAlertDialog.setCancelable(false);
    mAlertDialog.setButton(getString(R.string.ok), quitListener);
    mAlertDialog.setIcon(android.R.drawable.ic_dialog_info);
    mAlertShowing = true;
    mAlertMsg = message;
    mAlertDialog.show();
  }
  @Override
  public void setBinaryData(Object newImageObj) {
    // you are replacing an answer. delete the previous image using the
    // content provider.
    if (mBinaryName != null) {
      deleteMedia();
    }

    File newImage = (File) newImageObj;
    if (newImage.exists()) {
      // Add the new image to the Media content provider so that the
      // viewing is fast in Android 2.0+
      ContentValues values = new ContentValues(6);
      values.put(Images.Media.TITLE, newImage.getName());
      values.put(Images.Media.DISPLAY_NAME, newImage.getName());
      values.put(Images.Media.DATE_TAKEN, System.currentTimeMillis());
      values.put(Images.Media.MIME_TYPE, "image/jpeg");
      values.put(Images.Media.DATA, newImage.getAbsolutePath());

      Uri imageURI =
          getContext().getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values);
      Log.i(t, "Inserting image returned uri = " + imageURI.toString());

      mBinaryName = newImage.getName();
      Log.i(t, "Setting current answer to " + newImage.getName());
    } else {
      Log.e(t, "NO IMAGE EXISTS at: " + newImage.getAbsolutePath());
    }

    Collect.getInstance().getFormController().setIndexWaitingForData(null);
  }
 @Override
 public boolean onLongClick(View v) {
   Collect.getInstance()
       .getActivityLogger()
       .logAction(this, "toggleButton.longClick", Boolean.toString(mToggled));
   return showSentAndUnsentChoices();
 }
  @Override
  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if (!buttonView.isPressed()) {
      return;
    }
    if (!isChecked) {
      // If it got unchecked, we don't care.
      return;
    }

    for (RadioButton button : this.buttons) {
      if (button.isChecked() && !(buttonView == button)) {
        button.setChecked(false);
      }
    }
    Collect.getInstance()
        .getActivityLogger()
        .logInstanceAction(
            this,
            "onCheckedChanged",
            mItems.get((Integer) buttonView.getTag()).getValue(),
            mPrompt.getIndex());

    listener.advance();
  }
 @Override
 public boolean onMenuItemSelected(int featureId, MenuItem item) {
   switch (item.getItemId()) {
     case MENU_PREFERENCES:
       Collect.getInstance()
           .getActivityLogger()
           .logAction(this, "onMenuItemSelected", "MENU_PREFERENCES");
       createPreferencesMenu();
       return true;
     case MENU_SHOW_UNSENT:
       Collect.getInstance()
           .getActivityLogger()
           .logAction(this, "onMenuItemSelected", "MENU_SHOW_UNSENT");
       showSentAndUnsentChoices();
       return true;
   }
   return super.onMenuItemSelected(featureId, item);
 }
  /** Create a dialog with options to save and exit, save, or quit without saving */
  private void createQuitDrawDialog() {
    String[] items = {getString(R.string.keep_changes), getString(R.string.do_not_save)};

    Collect.getInstance()
        .getActivityLogger()
        .logInstanceAction(this, "createQuitDrawDialog", "show");
    alertDialog =
        new AlertDialog.Builder(this)
            .setIcon(android.R.drawable.ic_dialog_info)
            .setTitle(alertTitleString)
            .setNeutralButton(
                getString(R.string.do_not_exit),
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int id) {

                    Collect.getInstance()
                        .getActivityLogger()
                        .logInstanceAction(this, "createQuitDrawDialog", "cancel");
                    dialog.cancel();
                  }
                })
            .setItems(
                items,
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    switch (which) {
                      case 0: // save and exit
                        Collect.getInstance()
                            .getActivityLogger()
                            .logInstanceAction(this, "createQuitDrawDialog", "saveAndExit");
                        SaveAndClose();
                        break;

                      case 1: // discard changes and exit
                        Collect.getInstance()
                            .getActivityLogger()
                            .logInstanceAction(this, "createQuitDrawDialog", "discardAndExit");
                        CancelAndClose();
                        break;

                      case 2: // do nothing
                        Collect.getInstance()
                            .getActivityLogger()
                            .logInstanceAction(this, "createQuitDrawDialog", "cancel");
                        break;
                    }
                  }
                })
            .create();
    alertDialog.show();
  }
  private boolean showSentAndUnsentChoices() {
    /** Create a dialog with options to save and exit, save, or quit without saving */
    String[] items = {
      getString(R.string.show_unsent_forms), getString(R.string.show_sent_and_unsent_forms)
    };

    Collect.getInstance().getActivityLogger().logAction(this, "changeView", "show");

    AlertDialog alertDialog =
        new AlertDialog.Builder(this)
            .setIcon(android.R.drawable.ic_dialog_info)
            .setTitle(getString(R.string.change_view))
            .setNeutralButton(
                getString(R.string.cancel),
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int id) {
                    Collect.getInstance()
                        .getActivityLogger()
                        .logAction(this, "changeView", "cancel");
                    dialog.cancel();
                  }
                })
            .setItems(
                items,
                new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int which) {
                    switch (which) {
                      case 0: // show unsent
                        Collect.getInstance()
                            .getActivityLogger()
                            .logAction(this, "changeView", "showUnsent");
                        InstanceUploaderList.this.showUnsent();
                        break;

                      case 1: // show all
                        Collect.getInstance()
                            .getActivityLogger()
                            .logAction(this, "changeView", "showAll");
                        InstanceUploaderList.this.showAll();
                        break;

                      case 2: // do nothing
                        break;
                    }
                  }
                })
            .create();
    alertDialog.show();
    return true;
  }
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    Collect.getInstance().getActivityLogger().logAction(this, "onCreateOptionsMenu", "show");
    super.onCreateOptionsMenu(menu);

    CompatibilityUtils.setShowAsAction(
        menu.add(0, MENU_PREFERENCES, 0, R.string.general_preferences)
            .setIcon(R.drawable.ic_menu_preferences),
        MenuItem.SHOW_AS_ACTION_NEVER);
    CompatibilityUtils.setShowAsAction(
        menu.add(0, MENU_SHOW_UNSENT, 1, R.string.change_view).setIcon(R.drawable.ic_menu_manage),
        MenuItem.SHOW_AS_ACTION_NEVER);
    return true;
  }
  private void launchAnnotateActivity() {
    mErrorTextView.setVisibility(View.GONE);
    Intent i = new Intent(getContext(), DrawActivity.class);
    i.putExtra(DrawActivity.OPTION, DrawActivity.OPTION_ANNOTATE);
    // copy...
    if (mBinaryName != null) {
      File f = new File(mInstanceFolder + File.separator + mBinaryName);
      i.putExtra(DrawActivity.REF_IMAGE, Uri.fromFile(f));
    }
    i.putExtra(DrawActivity.EXTRA_OUTPUT, Uri.fromFile(new File(Collect.TMPFILE_PATH)));

    try {
      Collect.getInstance().getFormController().setIndexWaitingForData(mPrompt.getIndex());
      ((Activity) getContext()).startActivityForResult(i, FormEntryActivity.ANNOTATE_IMAGE);
    } catch (ActivityNotFoundException e) {
      Toast.makeText(
              getContext(),
              getContext().getString(R.string.activity_not_found, "annotate image"),
              Toast.LENGTH_SHORT)
          .show();
      Collect.getInstance().getFormController().setIndexWaitingForData(null);
    }
  }
  /** When the user clicks an item, authenticate against that account. */
  @Override
  protected void onListItemClick(ListView l, View v, int position, long id) {
    Account account = (Account) getListView().getItemAtPosition(position);
    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
    SharedPreferences.Editor editor = settings.edit();
    editor.remove(PreferencesActivity.KEY_AUTH);
    editor.putString(PreferencesActivity.KEY_ACCOUNT, account.name);
    editor.commit();

    Collect.getInstance().getActivityLogger().logAction(this, "select", account.name);
    Intent intent = new Intent(this, AccountInfo.class);
    intent.putExtra("account", account);
    startActivity(intent);
    finish();
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.notification_layout);

    String note = this.getIntent().getStringExtra(NOTIFICATION_KEY);
    if (note == null) {
      note = getString(R.string.notification_error);
    }

    TextView notificationText = (TextView) findViewById(R.id.notification);
    notificationText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, Collect.getQuestionFontsize());
    notificationText.setTypeface(null, Typeface.BOLD);
    notificationText.setPadding(0, 0, 0, 7);
    notificationText.setText(note);
  }
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // must be at the beginning of any activity that can be called from an external intent
    try {
      Collect.createODKDirs();
    } catch (RuntimeException e) {
      createErrorDialog(e.getMessage(), EXIT);
      return;
    }

    setContentView(R.layout.chooser_list_layout);
    setTitle(getString(R.string.enter_data));

    String sortOrder = FormsColumns.DISPLAY_NAME + " ASC, " + FormsColumns.JR_VERSION + " DESC";
    Cursor c = managedQuery(FormsColumns.CONTENT_URI, null, null, null, sortOrder);

    String[] data =
        new String[] {
          FormsColumns.DISPLAY_NAME, FormsColumns.DISPLAY_SUBTEXT, FormsColumns.JR_VERSION
        };
    int[] view = new int[] {R.id.text1, R.id.text2, R.id.text3};

    // render total instance view
    SimpleCursorAdapter instances =
        new VersionHidingCursorAdapter(
            FormsColumns.JR_VERSION, this, R.layout.two_item, c, data, view);
    setListAdapter(instances);

    if (savedInstanceState != null && savedInstanceState.containsKey(syncMsgKey)) {
      TextView tv = (TextView) findViewById(R.id.status_text);
      tv.setText(savedInstanceState.getString(syncMsgKey));
    }

    // DiskSyncTask checks the disk for any forms not already in the content provider
    // that is, put here by dragging and dropping onto the SDCard
    mDiskSyncTask = (DiskSyncTask) getLastNonConfigurationInstance();
    if (mDiskSyncTask == null) {
      Log.i(t, "Starting new disk sync task");
      mDiskSyncTask = new DiskSyncTask();
      mDiskSyncTask.setDiskSyncListener(this);
      mDiskSyncTask.setReloadForms(true);
      mDiskSyncTask.execute((Void[]) null);
    }
  }
  @Override
  protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);

    // get row id from db
    Cursor c = (Cursor) getListAdapter().getItem(position);
    long k = c.getLong(c.getColumnIndex(InstanceColumns._ID));

    Collect.getInstance().getActivityLogger().logAction(this, "onListItemClick", Long.toString(k));

    // add/remove from selected list
    if (mSelected.contains(k)) mSelected.remove(k);
    else mSelected.add(k);

    mUploadButton.setEnabled(!(mSelected.size() == 0));
    mSMSButton.setEnabled(!(mSelected.size() == 0));
  }
  @Override
  public void onCreate() {
    initializeSQLCipher();
    super.onCreate();
    instance = this;
    if (mExternalDirectoryPath == null) {
      mExternalDirectoryPath = this.getExternalFilesDir(null).toString();
      overrideODKDirs();
    }
    OpenMRS.createODKDirs();
    mLogger = new OpenMRSLogger();
    generateKey();
    OpenMRSDBOpenHelper.init();
    initializeDB();

    Intent i = new Intent(this, FormListService.class);
    startService(i);
  }
  private void readDictionary(int xmlId, Set<DictionaryItem> dictionary) {
    try {
      Context context = Collect.getInstance().getContext();
      XmlResourceParser parser = context.getResources().getXml(xmlId);

      int eventType = -1;
      dictionary.clear();

      if (parser != null) {
        parser.next();

        while (eventType != XmlPullParser.END_DOCUMENT) {
          if (eventType == XmlPullParser.START_TAG) {
            String tagName = parser.getName();

            if (tagName.equals("dictionary")) {
              eventType = parser.next();
              continue;
            }

            String tagId = parser.getAttributeValue(null, "id").trim();
            String tagValue = parser.getAttributeValue(null, "name").trim();
            String tagGroup = parser.getAttributeValue(null, "group").trim();

            dictionary.add(new DictionaryItem(tagId, tagValue, tagGroup));
          }

          eventType = parser.next();
        }

        parser.close();
      }
    } catch (Exception exception) {
      exception.printStackTrace();
    }
  }
  @Override
  protected Dialog onCreateDialog(int id) {
    switch (id) {
      case PROGRESS_DIALOG:
        Collect.getInstance()
            .getActivityLogger()
            .logAction(this, "onCreateDialog.PROGRESS_DIALOG", "show");

        mProgressDialog = new ProgressDialog(this);
        DialogInterface.OnClickListener loadingButtonListener =
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                Collect.getInstance()
                    .getActivityLogger()
                    .logAction(this, "onCreateDialog.PROGRESS_DIALOG", "cancel");
                dialog.dismiss();
                mInstanceUploaderTask.cancel(true);
                mInstanceUploaderTask.setUploaderListener(null);
                finish();
              }
            };
        mProgressDialog.setTitle(getString(R.string.uploading_data));
        mProgressDialog.setMessage(mAlertMsg);
        mProgressDialog.setIndeterminate(true);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        mProgressDialog.setCancelable(false);
        mProgressDialog.setButton(getString(R.string.cancel), loadingButtonListener);
        return mProgressDialog;
      case AUTH_DIALOG:
        Log.i(
            t,
            "onCreateDialog(AUTH_DIALOG): for upload of "
                + mInstancesToSend.length
                + " instances!");
        Collect.getInstance()
            .getActivityLogger()
            .logAction(this, "onCreateDialog.AUTH_DIALOG", "show");
        AlertDialog.Builder b = new AlertDialog.Builder(this);

        LayoutInflater factory = LayoutInflater.from(this);
        final View dialogView = factory.inflate(R.layout.server_auth_dialog, null);

        // Get the server, username, and password from the settings
        SharedPreferences settings =
            PreferenceManager.getDefaultSharedPreferences(getBaseContext());

        String server = mUrl;
        if (server == null) {
          Log.e(
              t,
              "onCreateDialog(AUTH_DIALOG): No failing mUrl specified for upload of "
                  + mInstancesToSend.length
                  + " instances!");
          // if the bundle is null, we're looking for a formlist
          String submissionUrl = getString(R.string.default_odk_submission);
          server =
              settings.getString(
                      PreferencesActivity.KEY_SERVER_URL, getString(R.string.default_server_url))
                  + settings.getString(PreferencesActivity.KEY_SUBMISSION_URL, submissionUrl);
        }

        final String url = server;

        Log.i(t, "Trying connecting to: " + url);

        EditText username = (EditText) dialogView.findViewById(R.id.username_edit);
        String storedUsername = settings.getString(PreferencesActivity.KEY_USERNAME, null);
        username.setText(storedUsername);

        EditText password = (EditText) dialogView.findViewById(R.id.password_edit);
        String storedPassword = settings.getString(PreferencesActivity.KEY_PASSWORD, null);
        password.setText(storedPassword);

        b.setTitle(getString(R.string.server_requires_auth));
        b.setMessage(getString(R.string.server_auth_credentials, url));
        b.setView(dialogView);
        b.setPositiveButton(
            R.string.ok,
            new DialogInterface.OnClickListener() {
              @Override
              public void onClick(DialogInterface dialog, int which) {
                Collect.getInstance()
                    .getActivityLogger()
                    .logAction(this, "onCreateDialog.AUTH_DIALOG", "OK");
                EditText username = (EditText) dialogView.findViewById(R.id.username_edit);
                EditText password = (EditText) dialogView.findViewById(R.id.password_edit);

                Uri u = Uri.parse(url);
                WebUtils.addCredentials(
                    username.getText().toString(), password.getText().toString(), u.getHost());

                showDialog(PROGRESS_DIALOG);
                mInstanceUploaderTask = new InstanceUploaderTask();

                // register this activity with the new uploader task
                mInstanceUploaderTask.setUploaderListener(InstanceUploaderActivity.this);

                mInstanceUploaderTask.execute(mInstancesToSend);
              }
            });
        b.setNegativeButton(
            R.string.cancel,
            new DialogInterface.OnClickListener() {

              @Override
              public void onClick(DialogInterface dialog, int which) {
                Collect.getInstance()
                    .getActivityLogger()
                    .logAction(this, "onCreateDialog.AUTH_DIALOG", "cancel");
                finish();
              }
            });

        b.setCancelable(false);
        return b.create();
    }
    return null;
  }
  private void readFormFeed(XmlPullParser parser, ArrayList<String> columns)
      throws XmlPullParserException, IOException, FormException {
    ArrayList<String> path = new ArrayList<String>();

    // we put path names in here as we go, and if we hit a duplicate we
    // blow up
    boolean getPaths = false;
    boolean inBody = false;
    int event = parser.next();
    int depth = 0;
    int lastpush = 0;
    while (event != XmlPullParser.END_DOCUMENT) {
      switch (event) {
        case XmlPullParser.START_TAG:
          if (parser.getName().equalsIgnoreCase("body")
              || parser.getName().equalsIgnoreCase("h:body")) {
            inBody = true;
          } else if (inBody && parser.getName().equalsIgnoreCase("repeat")) {
            throw new FormException(Collect.getInstance().getString(R.string.google_repeat_error));
          } else if (parser.getName().equalsIgnoreCase("upload")) {
            for (int i = 0; i < parser.getAttributeCount(); i++) {
              String attr = parser.getAttributeName(i);
              if (attr.startsWith("mediatype")) {
                String attrValue = parser.getAttributeValue(i);
                if (attrValue.startsWith("audio")) {
                  throw new FormException(
                      Collect.getInstance().getString(R.string.google_audio_error));
                } else if (attrValue.startsWith("video")) {
                  throw new FormException(
                      Collect.getInstance().getString(R.string.google_video_error));
                }
              }
            }
          }
          if (getPaths) {
            path.add(parser.getName());
            depth++;
            lastpush = depth;
          }
          if (parser.getName().equals("instance")) {
            getPaths = true;
          }
          break;
        case XmlPullParser.TEXT:
          // skip it
          break;
        case XmlPullParser.END_TAG:
          if (parser.getName().equals("body") || parser.getName().equals("h:body")) {
            inBody = false;
          }
          if (parser.getName().equals("instance")) {
            getPaths = false;
          }
          if (getPaths) {
            if (depth == lastpush) {
              columns.add(getPath(path));
            } else {
              lastpush--;
            }
            path.remove(path.size() - 1);
            depth--;
          }
          break;
        default:
          Log.i(tag, "DEFAULTING: " + parser.getName() + " :: " + parser.getEventType());
          break;
      }
      event = parser.next();
    }
  }
Exemple #22
0
  protected ItemsetWidget(
      Context context, FormEntryPrompt prompt, boolean readOnlyOverride, boolean derived) {
    super(context, prompt);
    mButtons = new RadioGroup(context);
    mButtons.setId(QuestionWidget.newUniqueId());
    mReadOnly = prompt.isReadOnly() || readOnlyOverride;
    mAnswers = new HashMap<String, String>();

    String currentAnswer = prompt.getAnswerText();

    // the format of the query should be something like this:
    // query="instance('cities')/root/item[state=/data/state and county=/data/county]"

    // "query" is what we're using to notify that this is an
    // itemset widget.
    String nodesetStr = prompt.getQuestion().getAdditionalAttribute(null, "query");

    // parse out the list name, between the ''
    String list_name =
        nodesetStr.substring(nodesetStr.indexOf("'") + 1, nodesetStr.lastIndexOf("'"));

    // isolate the string between between the [ ] characters
    String queryString =
        nodesetStr.substring(nodesetStr.indexOf("[") + 1, nodesetStr.lastIndexOf("]"));

    StringBuilder selection = new StringBuilder();
    // add the list name as the first argument, which will always be there
    selection.append("list_name=?");

    // check to see if there are any arguments
    if (queryString.indexOf("=") != -1) {
      selection.append(" and ");
    }

    // can't just split on 'and' or 'or' because they have different
    // behavior, so loop through and break them off until we don't have any
    // more
    // must include the spaces in indexOf so we don't match words like
    // "land"
    int andIndex = -1;
    int orIndex = -1;
    ArrayList<String> arguments = new ArrayList<String>();
    while ((andIndex = queryString.indexOf(" and ")) != -1
        || (orIndex = queryString.indexOf(" or ")) != -1) {
      if (andIndex != -1) {
        String subString = queryString.substring(0, andIndex);
        String pair[] = subString.split("=");
        if (pair.length == 2) {
          selection.append(pair[0].trim() + "=? and ");
          arguments.add(pair[1].trim());
        } else {
          // parse error
        }
        // move string forward to after " and "
        queryString = queryString.substring(andIndex + 5, queryString.length());
        andIndex = -1;
      } else if (orIndex != -1) {
        String subString = queryString.substring(0, orIndex);
        String pair[] = subString.split("=");
        if (pair.length == 2) {
          selection.append(pair[0].trim() + "=? or ");
          arguments.add(pair[1].trim());
        } else {
          // parse error
        }

        // move string forward to after " or "
        queryString = queryString.substring(orIndex + 4, queryString.length());
        orIndex = -1;
      }
    }

    // parse the last segment (or only segment if there are no 'and' or 'or'
    // clauses
    String pair[] = queryString.split("=");
    if (pair.length == 2) {
      selection.append(pair[0].trim() + "=?");
      arguments.add(pair[1].trim());
    }
    if (pair.length == 1) {
      // this is probably okay, because then you just list all items in
      // the list
    } else {
      // parse error
    }

    // +1 is for the list_name
    String[] selectionArgs = new String[arguments.size() + 1];

    boolean nullArgs = false; // can't have any null arguments
    selectionArgs[0] = list_name; // first argument is always listname

    // loop through the arguments, evaluate any expressions
    // and build the query string for the DB
    for (int i = 0; i < arguments.size(); i++) {
      XPathExpression xpr = null;
      try {
        xpr = XPathParseTool.parseXPath(arguments.get(i));
      } catch (XPathSyntaxException e) {
        e.printStackTrace();
        TextView error = new TextView(context);
        error.setText("XPathParser Exception:  \"" + arguments.get(i) + "\"");
        addView(error);
        break;
      }

      if (xpr != null) {
        FormDef form = Collect.getInstance().getFormController().getFormDef();
        TreeElement mTreeElement =
            form.getMainInstance().resolveReference(prompt.getIndex().getReference());
        EvaluationContext ec =
            new EvaluationContext(form.getEvaluationContext(), mTreeElement.getRef());
        Object value = xpr.eval(form.getMainInstance(), ec);

        if (value == null) {
          nullArgs = true;
        } else {
          if (value instanceof XPathNodeset) {
            XPathNodeset xpn = (XPathNodeset) value;
            value = xpn.getValAt(0);
          }

          selectionArgs[i + 1] = value.toString();
        }
      }
    }

    File itemsetFile =
        new File(
            Collect.getInstance().getFormController().getMediaFolder().getAbsolutePath()
                + "/itemsets.csv");
    if (nullArgs) {
      // we can't try to query with null values else it blows up
      // so just leave the screen blank
      // TODO: put an error?
    } else if (itemsetFile.exists()) {
      ItemsetDbAdapter ida = new ItemsetDbAdapter();
      ida.open();

      // name of the itemset table for this form
      String pathHash = ItemsetDbAdapter.getMd5FromString(itemsetFile.getAbsolutePath());
      try {
        Cursor c = ida.query(pathHash, selection.toString(), selectionArgs);
        if (c != null) {
          c.move(-1);
          while (c.moveToNext()) {
            String label = "";
            String val = "";
            // try to get the value associated with the label:lang
            // string if that doen't exist, then just use label
            String lang = "";
            if (Collect.getInstance().getFormController().getLanguages() != null
                && Collect.getInstance().getFormController().getLanguages().length > 0) {
              lang = Collect.getInstance().getFormController().getLanguage();
            }

            // apparently you only need the double quotes in the
            // column name when creating the column with a :
            // included
            String labelLang = "label" + "::" + lang;
            int langCol = c.getColumnIndex(labelLang);
            if (langCol == -1) {
              label = c.getString(c.getColumnIndex("label"));
            } else {
              label = c.getString(c.getColumnIndex(labelLang));
            }

            // the actual value is stored in name
            val = c.getString(c.getColumnIndex("name"));
            mAnswers.put(label, val);

            RadioButton rb = new RadioButton(context);
            rb.setOnCheckedChangeListener(this);
            rb.setText(label);
            rb.setTextSize(mAnswerFontsize);
            mButtons.addView(rb);
            // have to add it to the radiogroup before checking it,
            // else it lets two buttons be checked...
            if (currentAnswer != null && val.compareTo(currentAnswer) == 0) {
              rb.setChecked(true);
            }
          }
          c.close();
        }
      } finally {
        ida.close();
      }

      addView(mButtons);
    } else {
      TextView error = new TextView(context);
      error.setText(getContext().getString(R.string.file_missing, itemsetFile.getAbsolutePath()));
      addView(error);
    }
  }
 @Override
 protected void onStop() {
   Collect.getInstance().getActivityLogger().logOnStop(this);
   super.onStop();
 }
 @Override
 public void cancelWaitingForBinaryData() {
   Collect.getInstance().getFormController().setIndexWaitingForData(null);
 }
  public AnnotateWidget(Context context, FormEntryPrompt prompt) {
    super(context, prompt);

    mInstanceFolder = Collect.getInstance().getFormController().getInstancePath().getParent();

    setOrientation(LinearLayout.VERTICAL);

    TableLayout.LayoutParams params = new TableLayout.LayoutParams();
    params.setMargins(7, 5, 7, 5);

    mErrorTextView = new TextView(context);
    mErrorTextView.setId(QuestionWidget.newUniqueId());
    mErrorTextView.setText("Selected file is not a valid image");

    // setup capture button
    mCaptureButton = new Button(getContext());
    mCaptureButton.setId(QuestionWidget.newUniqueId());
    mCaptureButton.setText(getContext().getString(R.string.capture_image));
    mCaptureButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mAnswerFontsize);
    mCaptureButton.setPadding(20, 20, 20, 20);
    mCaptureButton.setEnabled(!prompt.isReadOnly());
    mCaptureButton.setLayoutParams(params);

    // launch capture intent on click
    mCaptureButton.setOnClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            Collect.getInstance()
                .getActivityLogger()
                .logInstanceAction(this, "captureButton", "click", mPrompt.getIndex());
            mErrorTextView.setVisibility(View.GONE);
            Intent i = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
            // We give the camera an absolute filename/path where to put the
            // picture because of bug:
            // http://code.google.com/p/android/issues/detail?id=1480
            // The bug appears to be fixed in Android 2.0+, but as of feb 2,
            // 2010, G1 phones only run 1.6. Without specifying the path the
            // images returned by the camera in 1.6 (and earlier) are ~1/4
            // the size. boo.

            // if this gets modified, the onActivityResult in
            // FormEntyActivity will also need to be updated.
            i.putExtra(
                android.provider.MediaStore.EXTRA_OUTPUT,
                Uri.fromFile(new File(Collect.TMPFILE_PATH)));
            try {
              Collect.getInstance().getFormController().setIndexWaitingForData(mPrompt.getIndex());
              ((Activity) getContext()).startActivityForResult(i, FormEntryActivity.IMAGE_CAPTURE);
            } catch (ActivityNotFoundException e) {
              Toast.makeText(
                      getContext(),
                      getContext().getString(R.string.activity_not_found, "image capture"),
                      Toast.LENGTH_SHORT)
                  .show();
              Collect.getInstance().getFormController().setIndexWaitingForData(null);
            }
          }
        });

    // setup chooser button
    mChooseButton = new Button(getContext());
    mChooseButton.setId(QuestionWidget.newUniqueId());
    mChooseButton.setText(getContext().getString(R.string.choose_image));
    mChooseButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mAnswerFontsize);
    mChooseButton.setPadding(20, 20, 20, 20);
    mChooseButton.setEnabled(!prompt.isReadOnly());
    mChooseButton.setLayoutParams(params);

    // launch capture intent on click
    mChooseButton.setOnClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            Collect.getInstance()
                .getActivityLogger()
                .logInstanceAction(this, "chooseButton", "click", mPrompt.getIndex());
            mErrorTextView.setVisibility(View.GONE);
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.setType("image/*");

            try {
              Collect.getInstance().getFormController().setIndexWaitingForData(mPrompt.getIndex());
              ((Activity) getContext()).startActivityForResult(i, FormEntryActivity.IMAGE_CHOOSER);
            } catch (ActivityNotFoundException e) {
              Toast.makeText(
                      getContext(),
                      getContext().getString(R.string.activity_not_found, "choose image"),
                      Toast.LENGTH_SHORT)
                  .show();
              Collect.getInstance().getFormController().setIndexWaitingForData(null);
            }
          }
        });

    // setup Blank Image Button
    mAnnotateButton = new Button(getContext());
    mAnnotateButton.setId(QuestionWidget.newUniqueId());
    mAnnotateButton.setText(getContext().getString(R.string.markup_image));
    mAnnotateButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mAnswerFontsize);
    mAnnotateButton.setPadding(20, 20, 20, 20);
    mAnnotateButton.setEnabled(false);
    mAnnotateButton.setLayoutParams(params);
    // launch capture intent on click
    mAnnotateButton.setOnClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            Collect.getInstance()
                .getActivityLogger()
                .logInstanceAction(this, "annotateButton", "click", mPrompt.getIndex());
            launchAnnotateActivity();
          }
        });

    // finish complex layout
    addView(mCaptureButton);
    addView(mChooseButton);
    addView(mAnnotateButton);
    addView(mErrorTextView);

    // and hide the capture, choose and annotate button if read-only
    if (prompt.isReadOnly()) {
      mCaptureButton.setVisibility(View.GONE);
      mChooseButton.setVisibility(View.GONE);
      mAnnotateButton.setVisibility(View.GONE);
    }
    mErrorTextView.setVisibility(View.GONE);

    // retrieve answer from data model and update ui
    mBinaryName = prompt.getAnswerText();

    // Only add the imageView if the user has taken a picture
    if (mBinaryName != null) {
      if (!prompt.isReadOnly()) {
        mAnnotateButton.setEnabled(true);
      }
      mImageView = new ImageView(getContext());
      mImageView.setId(QuestionWidget.newUniqueId());
      Display display =
          ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
              .getDefaultDisplay();
      int screenWidth = display.getWidth();
      int screenHeight = display.getHeight();

      File f = new File(mInstanceFolder + File.separator + mBinaryName);

      if (f.exists()) {
        Bitmap bmp = FileUtils.getBitmapScaledToDisplay(f, screenHeight, screenWidth);
        if (bmp == null) {
          mErrorTextView.setVisibility(View.VISIBLE);
        }
        mImageView.setImageBitmap(bmp);
      } else {
        mImageView.setImageBitmap(null);
      }

      mImageView.setPadding(10, 10, 10, 10);
      mImageView.setAdjustViewBounds(true);
      mImageView.setOnClickListener(
          new View.OnClickListener() {
            @Override
            public void onClick(View v) {
              Collect.getInstance()
                  .getActivityLogger()
                  .logInstanceAction(this, "viewImage", "click", mPrompt.getIndex());
              launchAnnotateActivity();
            }
          });

      addView(mImageView);
    }
  }
 @Override
 public boolean isWaitingForBinaryData() {
   return mPrompt
       .getIndex()
       .equals(Collect.getInstance().getFormController().getIndexWaitingForData());
 }
  private boolean uploadOneSubmission(
      String id, String instanceFilePath, String jrFormId, String token, String formFilePath) {
    // if the token is null fail immediately
    if (token == null) {
      mResults.put(id, oauth_fail + Collect.getInstance().getString(R.string.invalid_oauth));
      return false;
    }

    HashMap<String, String> answersToUpload = new HashMap<String, String>();
    HashMap<String, String> photosToUpload = new HashMap<String, String>();
    HashMap<String, PhotoEntry> uploadedPhotos = new HashMap<String, PhotoEntry>();

    HttpTransport h = AndroidHttp.newCompatibleTransport();
    GoogleCredential gc = new GoogleCredential();
    gc.setAccessToken(token);

    PicasaClient client = new PicasaClient(h.createRequestFactory(gc));

    // get instance file
    File instanceFile = new File(instanceFilePath);

    // first check to see how many columns we have:
    ArrayList<String> columnNames = new ArrayList<String>();
    try {
      getColumns(formFilePath, columnNames);
    } catch (FileNotFoundException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    } catch (XmlPullParserException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    } catch (IOException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    } catch (FormException e2) {
      e2.printStackTrace();
      mResults.put(id, e2.getMessage());
      return false;
    }

    if (columnNames.size() > 255) {
      mResults.put(
          id, Collect.getInstance().getString(R.string.sheets_max_columns, columnNames.size()));
      return false;
    }

    // make sure column names are legal
    for (String n : columnNames) {
      if (!isValidGoogleSheetsString(n)) {
        mResults.put(
            id, Collect.getInstance().getString(R.string.google_sheets_invalid_column_form, n));
        return false;
      }
    }

    // parses the instance file and populates the answers and photos
    // hashmaps.
    try {
      processInstanceXML(instanceFile, answersToUpload, photosToUpload);
    } catch (XmlPullParserException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (FormException e) {
      mResults.put(id, form_fail + Collect.getInstance().getString(R.string.google_repeat_error));
      return false;
    } catch (FileNotFoundException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    try {
      Thread.sleep(GOOGLE_SLEEP_TIME);
    } catch (InterruptedException e3) {
      e3.printStackTrace();
    }

    // make sure column names in submission are legal (may be different than form)
    for (String n : answersToUpload.keySet()) {
      if (!isValidGoogleSheetsString(n)) {
        mResults.put(
            id, Collect.getInstance().getString(R.string.google_sheets_invalid_column_instance, n));
        return false;
      }
    }

    // if we have any photos to upload,
    // get the picasa album or create a new one
    // then upload the photos
    if (photosToUpload.size() > 0) {
      // First set up a picasa album to upload to:
      // maybe we should move this, because if we don't have any
      // photos we don't care...
      AlbumEntry albumToUse;
      try {
        albumToUse = getOrCreatePicasaAlbum(client, jrFormId);
      } catch (IOException e) {
        e.printStackTrace();
        GoogleAuthUtil.invalidateToken(Collect.getInstance(), token);
        mResults.put(id, picasa_fail + e.getMessage());
        return false;
      }

      try {
        uploadPhotosToPicasa(photosToUpload, uploadedPhotos, client, albumToUse, instanceFile);
      } catch (IOException e1) {
        e1.printStackTrace();
        mResults.put(id, picasa_fail + e1.getMessage());
        return false;
      }
    }

    // All photos have been sent to picasa (if there were any)
    // now upload data to Google Sheet

    String selection = InstanceColumns._ID + "=?";
    String[] selectionArgs = {id};

    Cursor cursor = null;
    String urlString = null;
    try {
      // see if the submission element was defined in the form
      cursor =
          Collect.getInstance()
              .getContentResolver()
              .query(InstanceColumns.CONTENT_URI, null, selection, selectionArgs, null);

      if (cursor.getCount() > 0) {
        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
          int subIdx = cursor.getColumnIndex(InstanceColumns.SUBMISSION_URI);
          urlString = cursor.isNull(subIdx) ? null : cursor.getString(subIdx);

          // if we didn't find one in the content provider,
          // try to get from settings
          if (urlString == null) {
            SharedPreferences settings =
                PreferenceManager.getDefaultSharedPreferences(Collect.getInstance());
            urlString =
                settings.getString(
                    PreferencesActivity.KEY_GOOGLE_SHEETS_URL,
                    Collect.getInstance().getString(R.string.default_google_sheets_url));
          }
        }
      }
    } finally {
      if (cursor != null) {
        cursor.close();
      }
    }

    // now parse the url string if we have one
    final String googleHeader = "docs.google.com/spreadsheets/d/";
    String sheetId;
    if (urlString == null || urlString.length() < googleHeader.length()) {
      mResults.put(
          id, form_fail + Collect.getInstance().getString(R.string.invalid_sheet_id, urlString));
      return false;
    } else {
      int start = urlString.indexOf(googleHeader) + googleHeader.length();
      int end = urlString.indexOf("/", start);
      if (end == -1) {
        // if there wasn't a "/", just try to get the end
        end = urlString.length();
      }
      if (start == -1 || end == -1) {
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.invalid_sheet_id, urlString));
        return false;
      }
      sheetId = urlString.substring(start, end);
    }

    SpreadsheetService service = new SpreadsheetService("ODK-Collect");
    service.setAuthSubToken(token);

    // Define the URL to request.
    URL spreadsheetFeedURL = null;
    try {
      spreadsheetFeedURL =
          new URL("https://spreadsheets.google.com/feeds/worksheets/" + sheetId + "/private/full");
    } catch (MalformedURLException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    WorksheetQuery query = new WorksheetQuery(spreadsheetFeedURL);
    WorksheetFeed feed = null;
    try {
      feed = service.query(query, WorksheetFeed.class);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      if (e.getLocalizedMessage().equalsIgnoreCase("forbidden")) {
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.google_sheets_access_denied));
      } else {
        mResults.put(id, form_fail + Html.fromHtml(e.getResponseBody()));
      }
      return false;
    }

    List<WorksheetEntry> spreadsheets = feed.getEntries();
    // get the first worksheet
    WorksheetEntry we = spreadsheets.get(0);

    // check the headers....
    URL headerFeedUrl = null;
    try {
      headerFeedUrl =
          new URI(
                  we.getCellFeedUrl().toString()
                      + "?min-row=1&max-row=1&min-col=1&max-col="
                      + we.getColCount()
                      + "&return-empty=true")
              .toURL();
    } catch (MalformedURLException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    } catch (URISyntaxException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    }

    CellFeed headerFeed = null;
    try {
      headerFeed = service.getFeed(headerFeedUrl, CellFeed.class);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    boolean emptyheaders = true;

    // go through headers
    // if they're empty, resize and add
    for (CellEntry c : headerFeed.getEntries()) {
      if (c.getCell().getValue() != null) {
        emptyheaders = false;
        break;
      }
    }

    if (emptyheaders) {
      // if the headers were empty, resize the spreadsheet
      // and add the headers
      we.setColCount(columnNames.size());
      try {
        we.update();
      } catch (IOException e2) {
        e2.printStackTrace();
        mResults.put(id, form_fail + e2.getMessage());
        return false;
      } catch (ServiceException e2) {
        e2.printStackTrace();
        mResults.put(id, form_fail + e2.getMessage());
        return false;
      } catch (UnsupportedOperationException e) {
        e.printStackTrace();
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.google_sheets_update_error));
        return false;
      }

      // get the cell feed url
      URL cellFeedUrl = null;
      try {
        cellFeedUrl =
            new URI(
                    we.getCellFeedUrl().toString()
                        + "?min-row=1&max-row=1&min-col=1&max-col="
                        + columnNames.size()
                        + "&return-empty=true")
                .toURL();
      } catch (MalformedURLException e1) {
        e1.printStackTrace();
        mResults.put(id, form_fail + e1.getMessage());
        return false;
      } catch (URISyntaxException e1) {
        e1.printStackTrace();
        mResults.put(id, form_fail + e1.getMessage());
        return false;
      }

      // and the cell feed
      CellFeed cellFeed = null;
      try {
        cellFeed = service.getFeed(cellFeedUrl, CellFeed.class);
      } catch (IOException e) {
        e.printStackTrace();
        mResults.put(id, form_fail + e.getMessage());
        return false;
      } catch (ServiceException e) {
        e.printStackTrace();
        mResults.put(id, form_fail + e.getMessage());
        return false;
      }

      // write the headers
      for (int i = 0; i < cellFeed.getEntries().size(); i++) {
        CellEntry cell = cellFeed.getEntries().get(i);
        String column = columnNames.get(i);
        cell.changeInputValueLocal(column);
        try {
          cell.update();
        } catch (IOException e) {
          e.printStackTrace();
          mResults.put(id, form_fail + e.getMessage());
          return false;
        } catch (ServiceException e) {
          e.printStackTrace();
          mResults.put(id, form_fail + e.getMessage());
          return false;
        }
      }
    }

    // we may have updated the feed, so get a new one
    // update the feed
    try {
      headerFeedUrl =
          new URI(
                  we.getCellFeedUrl().toString()
                      + "?min-row=1&max-row=1&min-col=1&max-col="
                      + we.getColCount()
                      + "&return-empty=true")
              .toURL();
    } catch (MalformedURLException e3) {
      e3.printStackTrace();
      mResults.put(id, form_fail + e3.getMessage());
      return false;
    } catch (URISyntaxException e3) {
      e3.printStackTrace();
      mResults.put(id, form_fail + e3.getMessage());
      return false;
    }
    try {
      headerFeed = service.getFeed(headerFeedUrl, CellFeed.class);
    } catch (IOException e2) {
      e2.printStackTrace();
      mResults.put(id, form_fail + e2.getMessage());
      return false;
    } catch (ServiceException e2) {
      e2.printStackTrace();
      mResults.put(id, form_fail + e2.getMessage());
      return false;
    }

    // see if our columns match, now
    URL cellFeedUrl = null;
    try {
      cellFeedUrl =
          new URI(
                  we.getCellFeedUrl().toString()
                      + "?min-row=1&max-row=1&min-col=1&max-col="
                      + headerFeed.getEntries().size()
                      + "&return-empty=true")
              .toURL();
    } catch (MalformedURLException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    } catch (URISyntaxException e1) {
      e1.printStackTrace();
      mResults.put(id, form_fail + e1.getMessage());
      return false;
    }
    CellFeed cellFeed = null;
    try {
      cellFeed = service.getFeed(cellFeedUrl, CellFeed.class);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    }

    // first, get all the columns in the spreadsheet
    ArrayList<String> sheetCols = new ArrayList<String>();
    for (int i = 0; i < cellFeed.getEntries().size(); i++) {
      CellEntry cell = cellFeed.getEntries().get(i);
      sheetCols.add(cell.getPlainTextContent());
    }

    ArrayList<String> missingColumns = new ArrayList<String>();
    for (String col : columnNames) {
      if (!sheetCols.contains(col)) {
        missingColumns.add(col);
      }
    }

    if (missingColumns.size() > 0) {
      // we had some missing columns, so error out
      String missingString = "";
      for (int i = 0; i < missingColumns.size(); i++) {
        missingString += missingColumns.get(i);
        if (i < missingColumns.size() - 1) {
          missingString += ", ";
        }
      }
      mResults.put(
          id,
          form_fail
              + Collect.getInstance()
                  .getString(R.string.google_sheets_missing_columns, missingString));
      return false;
    }

    // if we get here.. all has matched
    // so write the values
    ListEntry row = new ListEntry();

    // add photos to answer set
    Iterator<String> photoIterator = uploadedPhotos.keySet().iterator();
    while (photoIterator.hasNext()) {
      String key = photoIterator.next();
      String url = uploadedPhotos.get(key).getImageLink();
      answersToUpload.put(key, url);
    }

    Iterator<String> answerIterator = answersToUpload.keySet().iterator();
    while (answerIterator.hasNext()) {
      String path = answerIterator.next();
      String answer = answersToUpload.get(path);
      // Check to see if answer is a location, if so, get rid of accuracy
      // and altitude
      // try to match a fairly specific pattern to determine
      // if it's a location
      // [-]#.# [-]#.# #.# #.#
      Pattern p =
          Pattern.compile(
              "^-?[0-9]+\\.[0-9]+\\s-?[0-9]+\\.[0-9]+\\s-?[0-9]+\\" + ".[0-9]+\\s[0-9]+\\.[0-9]+$");
      Matcher m = p.matcher(answer);
      if (m.matches()) {
        // get rid of everything after the second space
        int firstSpace = answer.indexOf(" ");
        int secondSpace = answer.indexOf(" ", firstSpace + 1);
        answer = answer.substring(0, secondSpace);
        answer = answer.replace(' ', ',');
      }
      row.getCustomElements().setValueLocal(TextUtils.htmlEncode(path), answer);
    }

    // Send the new row to the API for insertion.
    try {
      URL listFeedUrl = we.getListFeedUrl();
      row = service.insert(listFeedUrl, row);
    } catch (IOException e) {
      e.printStackTrace();
      mResults.put(id, form_fail + e.getMessage());
      return false;
    } catch (ServiceException e) {
      e.printStackTrace();
      if (e.getLocalizedMessage().equalsIgnoreCase("Forbidden")) {
        mResults.put(
            id, form_fail + Collect.getInstance().getString(R.string.google_sheets_access_denied));
      } else {
        mResults.put(id, form_fail + Html.fromHtml(e.getResponseBody()));
      }
      return false;
    }

    mResults.put(id, Collect.getInstance().getString(R.string.success));
    return true;
  }
  private void uploadPhotosToPicasa(
      HashMap<String, String> photos,
      HashMap<String, PhotoEntry> uploaded,
      PicasaClient client,
      AlbumEntry albumToUse,
      File instanceFile)
      throws IOException {
    Iterator<String> itr = photos.keySet().iterator();
    while (itr.hasNext()) {
      String key = itr.next();
      String filename = instanceFile.getParentFile() + "/" + photos.get(key);
      File toUpload = new File(filename);

      // first check the local content provider
      // to see if this photo already has a picasa_id
      String selection = Images.Media.DATA + "=?";
      String[] selectionArgs = {filename};
      Cursor c =
          Collect.getInstance()
              .getContentResolver()
              .query(Images.Media.EXTERNAL_CONTENT_URI, null, selection, selectionArgs, null);
      if (c.getCount() != 1) {
        c.close();
        throw new FileNotFoundException(
            picasa_fail + Collect.getInstance().getString(R.string.picasa_upload_error, filename));
      }

      // assume it's not already in picasa
      boolean inPicasa = false;

      // this will contain the link to the photo
      PhotoEntry picasaPhoto = null;

      c.moveToFirst();
      String picasa_id = c.getString(c.getColumnIndex(Images.Media.PICASA_ID));
      if (picasa_id == null || picasa_id.equalsIgnoreCase("")) {
        // not in picasa, so continue
      } else {
        // got a picasa ID, make sure it exists in this
        // particular album online
        // if it does, go on
        // if it doesn't, upload it and update the picasa_id
        if (albumToUse.numPhotos != 0) {
          PicasaUrl photosUrl = new PicasaUrl(albumToUse.getFeedLink());
          AlbumFeed albumFeed = client.executeGetAlbumFeed(photosUrl);

          for (PhotoEntry photo : albumFeed.photos) {
            if (picasa_id.equals(photo.id)) {
              // already in picasa, no need to upload
              inPicasa = true;
              picasaPhoto = photo;
            }
          }
        }
      }

      // wasn't already there, so upload a new copy and update the
      // content provder with its picasa_id
      if (!inPicasa) {
        String fileName = toUpload.getAbsolutePath();
        File file = new File(fileName);
        String mimetype = URLConnection.guessContentTypeFromName(file.getName());
        InputStreamContent content = new InputStreamContent(mimetype, new FileInputStream(file));

        picasaPhoto =
            client.executeInsertPhotoEntry(
                new PicasaUrl(albumToUse.getFeedLink()), content, toUpload.getName());

        ContentValues cv = new ContentValues();
        cv.put(Images.Media.PICASA_ID, picasaPhoto.id);

        // update the content provider picasa_id once we upload
        String where = Images.Media.DATA + "=?";
        String[] whereArgs = {toUpload.getAbsolutePath()};
        Collect.getInstance()
            .getContentResolver()
            .update(Images.Media.EXTERNAL_CONTENT_URI, cv, where, whereArgs);
      }

      // uploadedPhotos keeps track of the uploaded URL
      // relative to the path
      uploaded.put(key, picasaPhoto);
    }
  }
  @Override
  protected void onPostExecute(HashMap<String, String> results) {
    synchronized (this) {
      if (mStateListener != null) {
        mStateListener.uploadingComplete(results);

        if (results != null) {
          StringBuilder selection = new StringBuilder();
          Set<String> keys = results.keySet();
          Iterator<String> it = keys.iterator();

          String[] selectionArgs = new String[keys.size() + 1];
          int i = 0;
          selection.append("(");
          while (it.hasNext()) {
            String id = it.next();
            selection.append(InstanceColumns._ID + "=?");
            selectionArgs[i++] = id;
            if (i != keys.size()) {
              selection.append(" or ");
            }
          }

          selection.append(") and status=?");
          selectionArgs[i] = InstanceProviderAPI.STATUS_SUBMITTED;

          Cursor uploadResults = null;
          try {
            uploadResults =
                Collect.getInstance()
                    .getContentResolver()
                    .query(
                        InstanceColumns.CONTENT_URI,
                        null,
                        selection.toString(),
                        selectionArgs,
                        null);
            if (uploadResults.getCount() > 0) {
              Long[] toDelete = new Long[uploadResults.getCount()];
              uploadResults.moveToPosition(-1);

              int cnt = 0;
              while (uploadResults.moveToNext()) {
                toDelete[cnt] =
                    uploadResults.getLong(uploadResults.getColumnIndex(InstanceColumns._ID));
                cnt++;
              }

              boolean deleteFlag =
                  PreferenceManager.getDefaultSharedPreferences(
                          Collect.getInstance().getApplicationContext())
                      .getBoolean(PreferencesActivity.KEY_DELETE_AFTER_SEND, false);
              if (deleteFlag) {
                DeleteInstancesTask dit = new DeleteInstancesTask();
                dit.setContentResolver(Collect.getInstance().getContentResolver());
                dit.execute(toDelete);
              }
            }
          } finally {
            if (uploadResults != null) {
              uploadResults.close();
            }
          }
        }
      }
    }
  }
  /**
   * Retrieve the encryption information for this uri.
   *
   * @param mUri either an instance URI (if previously saved) or a form URI
   * @param instanceMetadata
   * @return
   */
  public static EncryptedFormInformation getEncryptedFormInformation(
      Uri mUri, FormController.InstanceMetadata instanceMetadata) {

    ContentResolver cr = Collect.getInstance().getContentResolver();

    // fetch the form information
    String formId;
    String formVersion;
    PublicKey pk;
    Base64Wrapper wrapper;

    Cursor formCursor = null;
    try {
      if (cr.getType(mUri) == InstanceProviderAPI.InstanceColumns.CONTENT_ITEM_TYPE) {
        // chain back to the Form record...
        String[] selectionArgs = null;
        String selection = null;
        Cursor instanceCursor = null;
        try {
          instanceCursor = cr.query(mUri, null, null, null, null);
          if (instanceCursor.getCount() != 1) {
            Log.e(t, "Not exactly one record for this instance!");
            return null; // save unencrypted.
          }
          instanceCursor.moveToFirst();
          String jrFormId =
              instanceCursor.getString(
                  instanceCursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.JR_FORM_ID));
          int idxJrVersion =
              instanceCursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.JR_VERSION);
          if (!instanceCursor.isNull(idxJrVersion)) {
            selectionArgs = new String[] {jrFormId, instanceCursor.getString(idxJrVersion)};
            selection =
                FormsProviderAPI.FormsColumns.JR_FORM_ID
                    + " =? AND "
                    + FormsProviderAPI.FormsColumns.JR_VERSION
                    + "=?";
          } else {
            selectionArgs = new String[] {jrFormId};
            selection =
                FormsProviderAPI.FormsColumns.JR_FORM_ID
                    + " =? AND "
                    + FormsProviderAPI.FormsColumns.JR_VERSION
                    + " IS NULL";
          }
        } finally {
          if (instanceCursor != null) {
            instanceCursor.close();
          }
        }

        formCursor =
            cr.query(
                FormsProviderAPI.FormsColumns.CONTENT_URI, null, selection, selectionArgs, null);

        if (formCursor.getCount() != 1) {
          Log.e(t, "Not exactly one blank form matches this jr_form_id");
          return null; // save unencrypted
        }
        formCursor.moveToFirst();
      } else if (cr.getType(mUri) == FormsProviderAPI.FormsColumns.CONTENT_ITEM_TYPE) {
        formCursor = cr.query(mUri, null, null, null, null);
        if (formCursor.getCount() != 1) {
          Log.e(t, "Not exactly one blank form!");
          return null; // save unencrypted.
        }
        formCursor.moveToFirst();
      }

      formId =
          formCursor.getString(formCursor.getColumnIndex(FormsProviderAPI.FormsColumns.JR_FORM_ID));
      if (formId == null || formId.length() == 0) {
        Log.e(t, "No FormId specified???");
        return null;
      }
      int idxVersion = formCursor.getColumnIndex(FormsProviderAPI.FormsColumns.JR_VERSION);
      int idxBase64RsaPublicKey =
          formCursor.getColumnIndex(FormsProviderAPI.FormsColumns.BASE64_RSA_PUBLIC_KEY);
      formVersion = formCursor.isNull(idxVersion) ? null : formCursor.getString(idxVersion);
      String base64RsaPublicKey =
          formCursor.isNull(idxBase64RsaPublicKey)
              ? null
              : formCursor.getString(idxBase64RsaPublicKey);

      if (base64RsaPublicKey == null || base64RsaPublicKey.length() == 0) {
        return null; // this is legitimately not an encrypted form
      }

      int version = android.os.Build.VERSION.SDK_INT;
      if (version < 8) {
        Log.e(t, "Phone does not support encryption.");
        return null; // save unencrypted
      }

      // this constructor will throw an exception if we are not
      // running on version 8 or above (if Base64 is not found).
      try {
        wrapper = new Base64Wrapper();
      } catch (ClassNotFoundException e) {
        Log.e(t, "Phone does not have Base64 class but API level is " + version);
        e.printStackTrace();
        return null; // save unencrypted
      }

      // OK -- Base64 decode (requires API Version 8 or higher)
      byte[] publicKey = wrapper.decode(base64RsaPublicKey);
      X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey);
      KeyFactory kf;
      try {
        kf = KeyFactory.getInstance(RSA_ALGORITHM);
      } catch (NoSuchAlgorithmException e) {
        Log.e(t, "Phone does not support RSA encryption.");
        e.printStackTrace();
        return null;
      }
      try {
        pk = kf.generatePublic(publicKeySpec);
      } catch (InvalidKeySpecException e) {
        e.printStackTrace();
        Log.e(t, "Invalid RSA public key.");
        return null;
      }
    } finally {
      if (formCursor != null) {
        formCursor.close();
      }
    }

    // submission must have an OpenRosa metadata block with a non-null
    // instanceID value.
    if (instanceMetadata.instanceId == null) {
      Log.e(t, "No OpenRosa metadata block or no instanceId defined in that block");
      return null;
    }

    // For now, prevent encryption if the BouncyCastle implementation is not present.
    // https://code.google.com/p/opendatakit/issues/detail?id=918
    try {
      Cipher.getInstance(EncryptionUtils.SYMMETRIC_ALGORITHM, ENCRYPTION_PROVIDER);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
      Log.e(t, "No BouncyCastle implementation of symmetric algorithm!");
      return null;
    } catch (NoSuchProviderException e) {
      e.printStackTrace();
      Log.e(t, "No BouncyCastle provider for implementation of symmetric algorithm!");
      return null;
    } catch (NoSuchPaddingException e) {
      e.printStackTrace();
      Log.e(t, "No BouncyCastle provider for padding implementation of symmetric algorithm!");
      return null;
    }

    return new EncryptedFormInformation(formId, formVersion, instanceMetadata, pk, wrapper);
  }