diff --git a/collect_app/src/androidTest/assets/forms/likert_test.xml b/collect_app/src/androidTest/assets/forms/likert_test.xml
new file mode 100755
index 00000000000..d53fdcfb161
--- /dev/null
+++ b/collect_app/src/androidTest/assets/forms/likert_test.xml
@@ -0,0 +1,419 @@
+
+
+
+ All widgets likert icon
+
+
+
+
+ Strongly Agree
+ jr://images/famous.jpg
+
+
+ Strongly Disagree
+ jr://images/famous.jpg
+
+
+ Agree
+ jr://images/famous.jpg
+
+
+ Agree
+ jr://images/famous.jpg
+
+
+ Strongly Disagree
+ jr://images/famous.jpg
+
+
+ Agree
+ jr://images/famous.jpg
+
+
+ Strongly Agree
+ jr://images/famous.jpg
+
+
+ Disagree
+ jr://images/famous.jpg
+
+
+ Strongly Agree
+ jr://images/famous.jpg
+
+
+ Neutral
+ jr://images/not_Found.jpg
+
+
+ Disagree
+ jr://images/famous.jpg
+
+
+ Strongly Disagree
+ jr://images/famous.jpg
+
+
+ Disagree
+ jr://images/famous.jpg
+
+
+ Neutral
+ jr://images/famous.jpg
+
+
+ A
+
+
+ B
+
+
+ C
+
+
+ A1
+
+
+ A2
+
+
+ A3
+
+
+ B1
+
+
+ B2
+
+
+ B3
+
+
+ C1
+
+
+ C2
+
+
+ C3
+
+
+ C4
+
+
+ A1A
+
+
+ A1B
+
+
+ B1A
+
+
+ B1B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ static_instance-level1-0
+ a
+
+ -
+ static_instance-level1-1
+ b
+
+ -
+ static_instance-level1-2
+ c
+
+
+
+
+
+ -
+ static_instance-level2-0
+ a
+ a1
+
+ -
+ static_instance-level2-1
+ a
+ a2
+
+ -
+ static_instance-level2-2
+ a
+ a3
+
+ -
+ static_instance-level2-3
+ b
+ b1
+
+ -
+ static_instance-level2-4
+ b
+ b2
+
+ -
+ static_instance-level2-5
+ b
+ b3
+
+ -
+ static_instance-level2-6
+ c
+ c1
+
+ -
+ static_instance-level2-7
+ c
+ c2
+
+ -
+ static_instance-level2-8
+ c
+ c3
+
+ -
+ static_instance-level2-9
+ c
+ c4
+
+
+
+
+
+ -
+ static_instance-level3-0
+ a1a
+ a1
+
+ -
+ static_instance-level3-1
+ a1b
+ a1
+
+ -
+ static_instance-level3-2
+ b1a
+ b1
+
+ -
+ static_instance-level3-3
+ b1b
+ b1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Likert type widget (happy case)
+ -
+
+ choice_0
+
+ -
+
+ choice_1
+
+ -
+
+ choice_2
+
+ -
+
+ choice_3
+
+ -
+
+ choice_4
+
+
+
+
+ Likert type widget with images (happy case)
+ -
+
+ strongly_disagree
+
+ -
+
+ disagree
+
+ -
+
+ neutral
+
+ -
+
+ agree
+
+ -
+
+ strongly_agree
+
+
+
+
+ Insufficient text provided
+ -
+
+ choice_0
+
+ -
+
+ choice_1
+
+ -
+
+ choice_2
+
+ -
+
+ choice_3
+
+
+
+
+ Insufficient images provided
+ -
+
+ strongly_disagree
+
+ -
+
+ disagree
+
+ -
+
+ neutral
+
+ -
+
+ agree
+
+ -
+
+ strongly_agree
+
+
+
+
+ Image cannot be found
+ -
+
+ strongly_disagree
+
+ -
+
+ disagree
+
+ -
+
+ neutral
+
+ -
+
+ agree
+
+ -
+
+ strongly_agree
+
+
+
+
+ When there is a missing Text
+ -
+
+ choice_0
+
+ -
+
+ choice_1
+
+ -
+
+ choice_2
+
+ -
+
+ choice_3
+
+
+
+
+
+
+ -
+
+ a
+
+ -
+
+ b
+
+ -
+
+ c
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/collect_app/src/androidTest/assets/media/famous.jpg b/collect_app/src/androidTest/assets/media/famous.jpg
new file mode 100644
index 00000000000..d8071cd75ce
Binary files /dev/null and b/collect_app/src/androidTest/assets/media/famous.jpg differ
diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/formentry/LikertTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/formentry/LikertTest.java
new file mode 100644
index 00000000000..07c8fc4a8ca
--- /dev/null
+++ b/collect_app/src/androidTest/java/org/odk/collect/android/formentry/LikertTest.java
@@ -0,0 +1,158 @@
+package org.odk.collect.android.formentry;
+
+import android.Manifest;
+
+import androidx.test.espresso.intent.rule.IntentsTestRule;
+import androidx.test.rule.GrantPermissionRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.odk.collect.android.R;
+import org.odk.collect.android.activities.FormEntryActivity;
+import org.odk.collect.android.support.CopyFormRule;
+import org.odk.collect.android.support.ResetStateRule;
+import org.odk.collect.android.test.FormLoadingUtils;
+
+import java.util.Collections;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.startsWith;
+import static org.odk.collect.android.test.CustomMatchers.withIndex;
+
+public class LikertTest {
+ private static final String LIKERT_TEST_FORM = "likert_test.xml";
+
+ @Rule
+ public IntentsTestRule activityTestRule = FormLoadingUtils.getFormActivityTestRuleFor(LIKERT_TEST_FORM);
+
+ @Rule
+ public RuleChain copyFormChain = RuleChain
+ .outerRule(GrantPermissionRule.grant(
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.CAMERA)
+ )
+ .around(new ResetStateRule())
+ .around(new CopyFormRule(LIKERT_TEST_FORM, Collections.singletonList("famous.jpg")));
+
+ @Test
+ public void allText_canClick() {
+ openWidgetList();
+ onView(withText("Likert Widget")).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isChecked()));
+ }
+
+ @Test
+ public void allImages_canClick() {
+ openWidgetList();
+ onView(withText("Likert Image Widget")).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isChecked()));
+ }
+
+ @Test
+ public void insufficientText_canClick() {
+ openWidgetList();
+ onView(withText("Likert Widget Error")).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isChecked()));
+ }
+
+ @Test
+ public void insufficientImages_canClick() {
+ openWidgetList();
+ onView(withText("Likert Image Widget Error")).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isChecked()));
+ }
+
+ @Test
+ public void missingImage_canClick() {
+ openWidgetList();
+ onView(withText("Likert Image Widget Error2")).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isChecked()));
+ }
+
+ @Test
+ public void missingText_canClick() {
+ openWidgetList();
+ onView(withText("Likert Missing text Error")).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isChecked()));
+ }
+
+ @Test
+ public void onlyOneRemainsClicked() {
+ openWidgetList();
+ onView(withText("Likert Image Widget")).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isChecked()));
+ onView(withIndex(withClassName(endsWith("RadioButton")), 2)).perform(click());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 2)).check(matches(isChecked()));
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).check(matches(isNotChecked()));
+ }
+
+ @Test
+ public void testImagesLoad() {
+ openWidgetList();
+ onView(withText("Likert Image Widget")).perform(click());
+
+ for (int i = 0; i < 5; i++) {
+ onView(withIndex(withClassName(endsWith("RadioButton")), i)).check(matches(isDisplayed()));
+ }
+ }
+
+ @Test
+ public void updateTest_SelectionChangeAtOneCascadeLevelWithLikert_ShouldUpdateNextLevels() {
+ openWidgetList();
+ onView(withText("Cascading likert")).perform(click());
+
+ // No choices should be shown for levels 2 and 3 when no selection is made for level 1
+ onView(withText(startsWith("Level1"))).perform(click());
+ onView(withText("A1")).check(doesNotExist());
+ onView(withText("B1")).check(doesNotExist());
+ onView(withText("C1")).check(doesNotExist());
+ onView(withText("A1A")).check(doesNotExist());
+
+ // Selecting C for level 1 should only reveal options for C at level 2
+ // and selecting C3 for level 2 shouldn't reveal options in level 3
+ onView(withIndex(withClassName(endsWith("RadioButton")), 2)).perform(click());
+ onView(withText("C1")).check(matches(isDisplayed()));
+ onView(withText("C4")).check(matches(isDisplayed()));
+ onView(withText("A1")).check(doesNotExist());
+ onView(withText("B1")).check(doesNotExist());
+ onView(withIndex(withClassName(endsWith("RadioButton")), 5)).perform(click());
+ onView(withText("A1A")).check(doesNotExist());
+
+ // Selecting A for level 1 should reveal options for A at level 2
+ onView(withIndex(withClassName(endsWith("RadioButton")), 0)).perform(click());
+ onView(withText("A1")).check(matches(isDisplayed()));
+ onView(withText("A1A")).check(doesNotExist());
+ onView(withText("B1")).check(doesNotExist());
+ onView(withText("C1")).check(doesNotExist());
+
+ // Selecting A1 for level 2 should reveal options for A1 at level 3
+ onView(withIndex(withClassName(endsWith("RadioButton")), 3)).perform(click());
+ onView(withText("A1A")).check(matches(isDisplayed()));
+ onView(withText("B1A")).check(doesNotExist());
+ onView(withText("B1")).check(doesNotExist());
+ onView(withText("C1")).check(doesNotExist());
+ }
+
+ private void openWidgetList() {
+ onView(withId(R.id.menu_goto)).perform(click());
+ }
+}
diff --git a/collect_app/src/main/java/org/odk/collect/android/utilities/WidgetAppearanceUtils.java b/collect_app/src/main/java/org/odk/collect/android/utilities/WidgetAppearanceUtils.java
index c5e555f860e..649e7a860f3 100644
--- a/collect_app/src/main/java/org/odk/collect/android/utilities/WidgetAppearanceUtils.java
+++ b/collect_app/src/main/java/org/odk/collect/android/utilities/WidgetAppearanceUtils.java
@@ -53,6 +53,7 @@ public class WidgetAppearanceUtils {
public static final String AUTOCOMPLETE = "autocomplete";
public static final String LIST_NO_LABEL = "list-nolabel";
public static final String LIST = "list";
+ public static final String LIKERT = "likert";
public static final String LABEL = "label";
public static final String IMAGE_MAP = "image-map";
public static final String NO_BUTTONS = "no-buttons";
diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/LikertWidget.java b/collect_app/src/main/java/org/odk/collect/android/widgets/LikertWidget.java
new file mode 100644
index 00000000000..170962ebce7
--- /dev/null
+++ b/collect_app/src/main/java/org/odk/collect/android/widgets/LikertWidget.java
@@ -0,0 +1,355 @@
+package org.odk.collect.android.widgets;
+
+import java.io.File;
+import java.util.HashMap;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.appcompat.widget.AppCompatRadioButton;
+
+import org.javarosa.core.model.SelectChoice;
+import org.javarosa.core.model.data.IAnswerData;
+import org.javarosa.core.model.data.SelectOneData;
+import org.javarosa.core.model.data.helper.Selection;
+import org.javarosa.core.reference.InvalidReferenceException;
+import org.javarosa.core.reference.ReferenceManager;
+import org.javarosa.form.api.FormEntryCaption;
+import org.odk.collect.android.R;
+import org.odk.collect.android.external.ExternalSelectChoice;
+import org.odk.collect.android.formentry.questions.QuestionDetails;
+import org.odk.collect.android.utilities.FileUtils;
+import org.odk.collect.android.utilities.ViewIds;
+
+import timber.log.Timber;
+
+@SuppressLint("ViewConstructor")
+public class LikertWidget extends ItemsWidget {
+
+ private LinearLayout view;
+ private RadioButton checkedButton;
+ private final LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, 1);
+ private final LayoutParams textViewParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ private final LayoutParams imageViewParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ private final LayoutParams radioButtonsParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ private final LayoutParams buttonViewParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ private final LayoutParams leftLineViewParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2);
+ private final LayoutParams rightLineViewParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2);
+
+ HashMap buttonsToName;
+
+ public LikertWidget(Context context, QuestionDetails questionDetails) {
+ super(context, questionDetails);
+
+ setMainViewLayoutParameters();
+ setStructures();
+
+ setButtonListener();
+ setSavedButton();
+ addAnswerView(view);
+ }
+
+ public void setMainViewLayoutParameters() {
+ view = new LinearLayout(getContext());
+ view.setOrientation(LinearLayout.HORIZONTAL);
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ view.setLayoutParams(params);
+ }
+
+ // Inserts the selected button from a saved state
+ public void setSavedButton() {
+ if (getFormEntryPrompt().getAnswerValue() != null) {
+ String name = ((Selection) getFormEntryPrompt().getAnswerValue()
+ .getValue()).getValue();
+ for (RadioButton bu: buttonsToName.keySet()) {
+ if (buttonsToName.get(bu).equals(name)) {
+ checkedButton = bu;
+ checkedButton.setChecked(true);
+ }
+ }
+ }
+ }
+
+ @Override
+ public IAnswerData getAnswer() {
+ if (checkedButton == null) {
+ return null;
+ } else {
+ int selectedIndex = -1;
+ for (int i = 0; i < items.size(); i++) {
+ if (items.get(i).getValue().equals(buttonsToName.get(checkedButton))) {
+ selectedIndex = i;
+ }
+ }
+ if (selectedIndex == -1) {
+ return null;
+ }
+ SelectChoice sc = items.get(selectedIndex);
+ return new SelectOneData(new Selection(sc));
+ }
+ }
+
+ @Override
+ protected void addAnswerView(View v) {
+ if (v == null) {
+ Timber.e("cannot add a null view as an answerView");
+ return;
+ }
+ // default place to add answer
+ RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
+ params.addRule(RelativeLayout.BELOW, getHelpTextLayout().getId());
+ params.setMargins(10, 0, 10, 0);
+ addView(v, params);
+ }
+
+ public void setStructures() {
+ buttonsToName = new HashMap<>();
+ for (int i = 0; i < items.size(); i++) {
+ RelativeLayout buttonView = new RelativeLayout(this.getContext());
+ buttonViewParams.addRule(CENTER_IN_PARENT, TRUE);
+ buttonView.setLayoutParams(buttonViewParams);
+ RadioButton button = getRadioButton(i);
+
+ buttonsToName.put(button, items.get(i).getValue());
+ buttonView.addView(button);
+
+ if (i == 0) {
+ addLine(true, false, button, buttonView);
+ } else if (i == items.size() - 1) {
+ addLine(false, true, button, buttonView);
+ } else {
+ addLine(false, false, button, buttonView);
+ }
+
+ LinearLayout optionView = getLinearLayout();
+ optionView.addView(buttonView);
+
+ ImageView imgView = getImageAsImageView(i);
+ // checks if image is set or valid
+ if (imgView != null) {
+ optionView.addView(imgView);
+ }
+ TextView choice = getTextView();
+ choice.setText(getFormEntryPrompt().getSelectChoiceText(items.get(i)));
+
+ optionView.addView(choice);
+
+ optionView.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ RadioButton r = button;
+ if (checkedButton != null) {
+ checkedButton.setChecked(false);
+ }
+ checkedButton = r;
+ checkedButton.setChecked(true);
+ widgetValueChanged();
+ }
+ });
+ view.addView(optionView);
+ }
+ }
+
+ // Adds lines to the button's side
+ public void addLine(boolean left, boolean right, RadioButton button, RelativeLayout buttonView) {
+ // left line
+ View leftLineView = new View(this.getContext());
+ leftLineViewParams.addRule(RelativeLayout.LEFT_OF, button.getId());
+ leftLineViewParams.addRule(CENTER_IN_PARENT, TRUE);
+ leftLineView.setLayoutParams(leftLineViewParams);
+ leftLineView.setBackgroundColor(getResources().getColor(R.color.gray600));
+
+ // right line
+ View rightLineView = new View(this.getContext());
+ rightLineViewParams.addRule(RelativeLayout.RIGHT_OF, button.getId());
+ rightLineViewParams.addRule(CENTER_IN_PARENT, TRUE);
+ rightLineView.setLayoutParams(rightLineViewParams);
+ rightLineView.setBackgroundColor(getResources().getColor(R.color.gray600));
+
+ if (left) {
+ if (isRTL()) {
+ rightLineView.setVisibility(View.INVISIBLE);
+ } else {
+ leftLineView.setVisibility(View.INVISIBLE);
+ }
+ }
+ buttonView.addView(leftLineView);
+ if (right) {
+ if (isRTL()) {
+ leftLineView.setVisibility(View.INVISIBLE);
+ } else {
+ rightLineView.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ buttonView.addView(rightLineView);
+ }
+
+ // Creates image view for choice
+ public ImageView getImageView() {
+ ImageView view = new ImageView(getContext());
+ view.setLayoutParams(imageViewParams);
+ return view;
+ }
+
+ public RadioButton getRadioButton(int i) {
+ AppCompatRadioButton button = new AppCompatRadioButton(getContext());
+ button.setId(ViewIds.generateViewId());
+ button.setEnabled(!getFormEntryPrompt().isReadOnly());
+ button.setFocusable(!getFormEntryPrompt().isReadOnly());
+ radioButtonsParams.addRule(CENTER_HORIZONTAL, TRUE);
+ button.setLayoutParams(radioButtonsParams);
+ // This the adds the negated margins to reduce the extra padding of the button.
+ // It is done this way to get the width of the button which has to be done after rendering
+ ViewTreeObserver vto = button.getViewTreeObserver();
+ // This variable is to prevent an infinite loop for rendering the button.
+ final Boolean[] paramsSet = {false};
+ vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!paramsSet[0]) {
+ int width = button.getWidth();
+ radioButtonsParams.setMargins(-width / 5, 0, -width / 5, 0);
+ button.setLayoutParams(radioButtonsParams);
+ paramsSet[0] = true;
+ }
+ }
+ });
+ button.setGravity(Gravity.CENTER);
+ return button;
+ }
+
+ // Creates text view for choice
+ public TextView getTextView() {
+ TextView view = new TextView(getContext());
+ view.setGravity(Gravity.CENTER);
+ view.setPadding(2, 2, 2, 2);
+ view.setLayoutParams(textViewParams);
+ return view;
+ }
+
+ // Linear Layout for new choice
+ public LinearLayout getLinearLayout() {
+ LinearLayout optionView = new LinearLayout(getContext());
+ optionView.setGravity(Gravity.CENTER);
+ optionView.setLayoutParams(linearLayoutParams);
+ linearLayoutParams.setMargins(-1, 0, -1, 0);
+ optionView.setOrientation(LinearLayout.VERTICAL);
+ return optionView;
+ }
+
+ public void setButtonListener() {
+ for (RadioButton button: buttonsToName.keySet()) {
+ button.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ RadioButton r = (RadioButton) v;
+ if (checkedButton != null) {
+ checkedButton.setChecked(false);
+ }
+ checkedButton = r;
+ checkedButton.setChecked(true);
+ widgetValueChanged();
+ }
+ });
+ }
+ }
+
+ public ImageView getImageAsImageView(int index) {
+ ImageView view = getImageView();
+ String imageURI;
+ if (items.get(index) instanceof ExternalSelectChoice) {
+ imageURI = ((ExternalSelectChoice) items.get(index)).getImage();
+ } else {
+ imageURI = getFormEntryPrompt().getSpecialFormSelectChoiceText(items.get(index),
+ FormEntryCaption.TEXT_FORM_IMAGE);
+ }
+ if (imageURI != null) {
+ String error = setImageFromOtherSource(imageURI, view);
+ if (error != null) {
+ return null;
+ }
+ return view;
+ } else {
+ return null;
+ }
+ }
+
+ public String setImageFromOtherSource(String imageURI, ImageView imageView) {
+ String errorMsg = null;
+ try {
+ String imageFilename =
+ ReferenceManager.instance().DeriveReference(imageURI).getLocalURI();
+ final File imageFile = new File(imageFilename);
+ if (imageFile.exists()) {
+ Bitmap b = null;
+ try {
+ DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ int screenWidth = metrics.widthPixels;
+ int screenHeight = metrics.heightPixels;
+ b = FileUtils.getBitmapScaledToDisplay(imageFile, screenHeight, screenWidth);
+ } catch (OutOfMemoryError e) {
+ errorMsg = "ERROR: " + e.getMessage();
+ }
+
+ if (b != null) {
+ imageView.setAdjustViewBounds(true);
+ imageView.setImageBitmap(b);
+ } else if (errorMsg == null) {
+ // Loading the image failed. The image work when in .jpg format
+ errorMsg = getContext().getString(R.string.file_invalid, imageFile);
+
+ }
+ } else {
+ errorMsg = getContext().getString(R.string.file_missing, imageFile);
+ }
+ if (errorMsg != null) {
+ Timber.e(errorMsg);
+ }
+
+ } catch (InvalidReferenceException e) {
+ Timber.e(e, "Invalid image reference due to %s ", e.getMessage());
+ }
+ return errorMsg;
+ }
+
+ @Override
+ public void setOnLongClickListener(OnLongClickListener l) {
+ for (RadioButton r : buttonsToName.keySet()) {
+ r.setOnLongClickListener(l);
+ }
+ }
+
+ @Override
+ public void cancelLongPress() {
+ super.cancelLongPress();
+ for (RadioButton r : buttonsToName.keySet()) {
+ r.cancelLongPress();
+ }
+ }
+
+ @Override
+ public void clearAnswer() {
+ if (checkedButton != null) {
+ checkedButton.setChecked(false);
+ }
+ checkedButton = null;
+ widgetValueChanged();
+ }
+}
diff --git a/collect_app/src/main/java/org/odk/collect/android/widgets/WidgetFactory.java b/collect_app/src/main/java/org/odk/collect/android/widgets/WidgetFactory.java
index cf1bee06699..edee7885a60 100644
--- a/collect_app/src/main/java/org/odk/collect/android/widgets/WidgetFactory.java
+++ b/collect_app/src/main/java/org/odk/collect/android/widgets/WidgetFactory.java
@@ -146,6 +146,8 @@ public static QuestionWidget createWidgetFromPrompt(FormEntryPrompt prompt, Cont
questionWidget = new SpinnerWidget(context, questionDetails, appearance.contains(WidgetAppearanceUtils.QUICK));
} else if (appearance.contains(WidgetAppearanceUtils.SEARCH) || appearance.contains(WidgetAppearanceUtils.AUTOCOMPLETE)) {
questionWidget = new SelectOneSearchWidget(context, questionDetails, appearance.contains(WidgetAppearanceUtils.QUICK));
+ } else if (appearance.contains(WidgetAppearanceUtils.LIKERT)) {
+ questionWidget = new LikertWidget(context, questionDetails);
} else if (appearance.contains(WidgetAppearanceUtils.LIST_NO_LABEL)) {
questionWidget = new ListWidget(context, questionDetails, false, appearance.contains(WidgetAppearanceUtils.QUICK));
} else if (appearance.contains(WidgetAppearanceUtils.LIST)) {