@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(); } } }
@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 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); }
/** * 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(); }
/** 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(); }
@Override public boolean onLongClick(View v) { Collect.getInstance() .getActivityLogger() .logAction(this, "toggleButton.longClick", Boolean.toString(mToggled)); return showSentAndUnsentChoices(); }
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 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; }
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); } }
@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; }
/** 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 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)); }
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(); } }
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(); } }
@Override protected void onStop() { Collect.getInstance().getActivityLogger().logOnStop(this); super.onStop(); }
@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; }
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); } }
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 public boolean isWaitingForBinaryData() { return mPrompt .getIndex() .equals(Collect.getInstance().getFormController().getIndexWaitingForData()); }
@Override public void cancelWaitingForBinaryData() { Collect.getInstance().getFormController().setIndexWaitingForData(null); }
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 public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.instance_uploader_list); // set up long click listener mUploadButton = (Button) findViewById(R.id.upload_button); mUploadButton.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = connectivityManager.getActiveNetworkInfo(); if (NetworkReceiver.running == true) { Toast.makeText( InstanceUploaderList.this, "Background send running, please try again shortly", Toast.LENGTH_SHORT) .show(); } else if (ni == null || !ni.isConnected()) { Collect.getInstance() .getActivityLogger() .logAction(this, "uploadButton", "noConnection"); Toast.makeText(InstanceUploaderList.this, R.string.no_connection, Toast.LENGTH_SHORT) .show(); } else { Collect.getInstance() .getActivityLogger() .logAction(this, "uploadButton", Integer.toString(mSelected.size())); if (mSelected.size() > 0) { // items selected uploadSelectedFiles(); mToggled = false; mSelected.clear(); InstanceUploaderList.this.getListView().clearChoices(); mUploadButton.setEnabled(false); mSMSButton.setEnabled(false); } else { // no items selected Toast.makeText( getApplicationContext(), getString(R.string.noselect_error), Toast.LENGTH_SHORT) .show(); } } } }); mSMSButton = (Button) findViewById(R.id.upload_sms_button); // Show "Send as SMS" button if using Medic Mobile platform AND // a SMS Gateway number is set in preferences Collect instance = Collect.getInstance(); mPrefs = PreferenceManager.getDefaultSharedPreferences(instance); String protocol = mPrefs.getString( PreferencesActivity.KEY_PROTOCOL, instance.getString(R.string.protocol_odk_default)); if (!protocol.equals(instance.getString(R.string.protocol_medic_mobile)) || mPrefs .getString( PreferencesActivity.KEY_SMS_GATEWAY, instance.getString(R.string.default_sms_gateway)) .isEmpty()) { mSMSButton.setVisibility(View.GONE); } else { mSMSButton.setVisibility(View.VISIBLE); } mSMSButton.setOnClickListener( new OnClickListener() { @Override public void onClick(View arg0) { Collect.getInstance() .getActivityLogger() .logAction(this, "uploadButton", Integer.toString(mSelected.size())); if (mSelected.size() > 0) { // items selected smsSelectedFiles(); mToggled = false; mSelected.clear(); InstanceUploaderList.this.getListView().clearChoices(); mUploadButton.setEnabled(false); mSMSButton.setEnabled(false); } else { // no items selected Toast.makeText( getApplicationContext(), getString(R.string.noselect_error), Toast.LENGTH_SHORT) .show(); } } }); mToggleButton = (Button) findViewById(R.id.toggle_button); mToggleButton.setLongClickable(true); mToggleButton.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { // toggle selections of items to all or none ListView ls = getListView(); mToggled = !mToggled; Collect.getInstance() .getActivityLogger() .logAction(this, "toggleButton", Boolean.toString(mToggled)); // remove all items from selected list mSelected.clear(); for (int pos = 0; pos < ls.getCount(); pos++) { ls.setItemChecked(pos, mToggled); // add all items if mToggled sets to select all if (mToggled) mSelected.add(ls.getItemIdAtPosition(pos)); } mUploadButton.setEnabled(!(mSelected.size() == 0)); mSMSButton.setEnabled(!(mSelected.size() == 0)); } }); mToggleButton.setOnLongClickListener(this); Cursor c = mShowUnsent ? getUnsentCursor() : getAllCursor(); String[] data = new String[] {InstanceColumns.DISPLAY_NAME, InstanceColumns.DISPLAY_SUBTEXT}; int[] view = new int[] {R.id.text1, R.id.text2}; // render total instance view mInstances = new SimpleCursorAdapter(this, R.layout.two_item_multiple_choice, c, data, view); setListAdapter(mInstances); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); getListView().setItemsCanFocus(false); mUploadButton.setEnabled(!(mSelected.size() == 0)); mSMSButton.setEnabled(!(mSelected.size() == 0)); // set title setTitle(getString(R.string.send_data)); // if current activity is being reinitialized due to changing // orientation restore all check // marks for ones selected if (mRestored) { ListView ls = getListView(); for (long id : mSelected) { for (int pos = 0; pos < ls.getCount(); pos++) { if (id == ls.getItemIdAtPosition(pos)) { ls.setItemChecked(pos, true); break; } } } mRestored = false; } }
/** * 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); }
@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(); } } } } } }