Java Code Examples for android.view.accessibility.AccessibilityEvent#getFromIndex()

The following examples show how to use android.view.accessibility.AccessibilityEvent#getFromIndex() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: ScrollEventInterpreter.java    From talkback with Apache License 2.0 6 votes vote down vote up
private boolean hasValidIndex(AccessibilityEvent event) {
  switch (event.getEventType()) {
    case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
      return (event.getFromIndex() != INDEX_UNDEFINED)
          && (event.getToIndex() != INDEX_UNDEFINED)
          && (event.getItemCount() > 0);
    case AccessibilityEvent.TYPE_VIEW_SCROLLED:
      boolean validScrollDelta = AccessibilityEventUtils.hasValidScrollDelta(event);
      return (event.getFromIndex() != INDEX_UNDEFINED)
          || (event.getToIndex() != INDEX_UNDEFINED)
          || (event.getScrollX() != INDEX_UNDEFINED)
          || (event.getScrollY() != INDEX_UNDEFINED)
          || validScrollDelta;
    default:
      return false;
  }
}
 
Example 2
Source File: AccessibilityEventProcessor.java    From PUMA with Apache License 2.0 6 votes vote down vote up
private static void dump(AccessibilityEvent event) {
	AccessibilityNodeInfo source = event.getSource();
	if (source == null) {
		Util.err("event source: NULL");
		return;
	}

	Rect bounds = new Rect();
	source.getBoundsInScreen(bounds);
	int cnt = -1;
	if (source.getClassName().equals(ListView.class.getCanonicalName())) {
		cnt = event.getItemCount();
		int from = event.getFromIndex();
		int to = event.getToIndex();
		Util.log(event.getEventTime() + ": " + AccessibilityEvent.eventTypeToString(event.getEventType()) + "," + source.getClassName() + "," + cnt + ", [" + from + " --> " + to + "], "
				+ bounds.toShortString());
	} else {
		Util.log(event.getEventTime() + ": " + AccessibilityEvent.eventTypeToString(event.getEventType()) + "," + source.getClassName() + "," + bounds.toShortString());
	}
}
 
Example 3
Source File: ProcessorPhoneticLetters.java    From talkback with Apache License 2.0 6 votes vote down vote up
/** Handle an event that indicates a text is being traversed at character granularity. */
private void processTraversalEvent(AccessibilityEvent event, EventId eventId) {
  final CharSequence text = AccessibilityEventUtils.getEventTextOrDescription(event);
  if (TextUtils.isEmpty(text)) {
    return;
  }

  String letter;
  if ((event.getAction() == AccessibilityNodeInfoCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
          || event.getAction()
              == AccessibilityNodeInfoCompat.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY)
      && event.getFromIndex() >= 0
      && event.getFromIndex() < text.length()) {
    letter = String.valueOf(text.charAt(event.getFromIndex()));
  } else {
    return;
  }
  speakPhoneticLetterForTraversedText(
      TextUtils.equals(event.getPackageName(), PackageManagerUtils.TALBACK_PACKAGE),
      letter,
      eventId);
}
 
Example 4
Source File: ScrollFeedbackManager.java    From talkback with Apache License 2.0 6 votes vote down vote up
private CharSequence getDescriptionForPageEvent(
    AccessibilityEvent event, AccessibilityNodeInfo source) {
  final int fromIndex = (event.getFromIndex() + 1);
  final int itemCount = event.getItemCount();
  if ((fromIndex <= 0) || (itemCount <= 0)) {
    return null;
  }

  CharSequence pageTitle = getSelectedPageTitle(source);
  if (!TextUtils.isEmpty(pageTitle)) {
    CharSequence count =
        context.getString(R.string.template_viewpager_index_count_short, fromIndex, itemCount);

    SpannableStringBuilder output = new SpannableStringBuilder();
    StringBuilderUtils.appendWithSeparator(output, pageTitle, count);
    return output;
  }

  return context.getString(R.string.template_viewpager_index_count, fromIndex, itemCount);
}
 
Example 5
Source File: ScrollFeedbackManager.java    From talkback with Apache License 2.0 6 votes vote down vote up
private CharSequence getDescriptionForScrollEvent(AccessibilityEvent event) {
  // If the from index or item count are invalid, don't announce anything.
  final int fromIndex = (event.getFromIndex() + 1);
  final int itemCount = event.getItemCount();
  if ((fromIndex <= 0) || (itemCount <= 0)) {
    return null;
  }

  // If the to and from indices are the same, or if the to index is
  // invalid, only announce the item at the from index.
  final int toIndex = event.getToIndex() + 1;
  if ((fromIndex == toIndex) || (toIndex <= 0) || (toIndex > itemCount)) {
    return context.getString(R.string.template_scroll_from_count, fromIndex, itemCount);
  }

  // Announce the range of visible items.
  return context.getString(R.string.template_scroll_from_to_count, fromIndex, toIndex, itemCount);
}
 
Example 6
Source File: ScrollFeedbackManager.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Returns whether the event is a duplicate of the previous event, or the event is triggered by
 * auto-scroll.
 *
 * @param event The event from which information about the scroll position will be retrieved
 * @return {@code true} if the event is a duplicate of the previous event, or triggered by
 *     auto-scroll
 */
protected boolean isDuplicateScrollEventOrAutoScroll(AccessibilityEvent event) {
  int eventType = event.getEventType();
  if ((eventType != AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED)
      && (eventType != AccessibilityEventCompat.TYPE_VIEW_SCROLLED)) {
    return false;
  }

  final int fromIndex = event.getFromIndex() + 1;
  final int itemCount = event.getItemCount();
  if (itemCount <= 0 || fromIndex <= 0) {
    return true;
  }

  EventId eventId;
  try {
    eventId = new EventId(event);
  } catch (Exception e) {
    return true;
  }

  final Integer cachedFromIndex = cachedFromValues.get(eventId);
  final Integer cachedItemCount = cachedItemCounts.get(eventId);

  if ((cachedFromIndex != null)
      && (cachedFromIndex == fromIndex)
      && (cachedItemCount != null)
      && (cachedItemCount == itemCount)) {
    // The from index hasn't changed, which means the event is coming
    // from a re-layout or resize and should not be spoken.
    return true;
  }

  // The behavior of put() for an existing key is unspecified, so we can't
  // recycle the old or new key nodes.
  cachedFromValues.put(eventId, fromIndex);
  cachedItemCounts.put(eventId, itemCount);

  return false;
}
 
Example 7
Source File: ScrollEventInterpreter.java    From talkback with Apache License 2.0 5 votes vote down vote up
PositionInfo(AccessibilityEvent event) {
  fromIndex = event.getFromIndex();
  toIndex = event.getToIndex();
  scrollX = event.getScrollX();
  scrollY = event.getScrollY();
  itemCount = event.getItemCount();
  scrollDeltaX = AccessibilityEventUtils.getScrollDeltaX(event);
  scrollDeltaY = AccessibilityEventUtils.getScrollDeltaY(event);
}
 
Example 8
Source File: LaunchApp.java    From PUMA with Apache License 2.0 5 votes vote down vote up
private boolean compareScrollEvent(AccessibilityEvent event1, AccessibilityEvent event2) {
	boolean same = false;

	if (event1 == null && event2 == null) {
		same = true;
	} else if (event1 != null && event2 != null) {
		String s1 = event1.getFromIndex() + "," + event1.getToIndex() + "," + event1.getScrollX() + "," + event1.getScrollY();
		String s2 = event2.getFromIndex() + "," + event2.getToIndex() + "," + event2.getScrollX() + "," + event2.getScrollY();
		same = s1.equals(s2);
	}

	return same;
}
 
Example 9
Source File: InputFocusInterpreter.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Gets target child node from the source AdapterView node.
 *
 * <p><strong>Note:</strong> Caller is responsible for recycling the returned node.
 */
private static @Nullable AccessibilityNodeInfoCompat getTargetChildFromAdapterView(
    AccessibilityEvent event) {
  AccessibilityNodeInfoCompat sourceNode = null;
  try {
    sourceNode = AccessibilityNodeInfoUtils.toCompat(event.getSource());
    if (sourceNode == null) {
      return null;
    }

    if (event.getItemCount() <= 0
        || event.getFromIndex() < 0
        || event.getCurrentItemIndex() < 0) {
      return null;
    }
    int index = event.getCurrentItemIndex() - event.getFromIndex();
    if (index < 0 || index >= sourceNode.getChildCount()) {
      return null;
    }
    AccessibilityNodeInfoCompat targetChildNode = sourceNode.getChild(index);

    // TODO: Think about to replace childNode check with sourceNode check.
    if ((targetChildNode == null)
        || !AccessibilityNodeInfoUtils.isTopLevelScrollItem(targetChildNode)) {
      AccessibilityNodeInfoUtils.recycleNodes(targetChildNode);
      return null;
    } else {
      return targetChildNode;
    }
  } finally {
    AccessibilityNodeInfoUtils.recycleNodes(sourceNode);
  }
}
 
Example 10
Source File: AccessibilityEventUtils.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Returns a floating point value representing the scroll position of an {@link
 * AccessibilityEvent}. This value may be outside the range {0..1}. If there's no valid way to
 * obtain a position, this method returns the default value.
 *
 * @param event The event from which to obtain the scroll position.
 * @param defaultValue Value to return if there is no valid scroll position from the event.
 * @return A floating point value representing the scroll position.
 */
public static float getScrollPosition(AccessibilityEvent event, float defaultValue) {
  if (event == null) {
    return defaultValue;
  }

  final int itemCount = event.getItemCount();
  final int fromIndex = event.getFromIndex();

  // First, attempt to use (fromIndex / itemCount).
  if ((fromIndex >= 0) && (itemCount > 0)) {
    return (fromIndex / (float) itemCount);
  }

  final int scrollY = event.getScrollY();
  final int maxScrollY = event.getMaxScrollY();

  // Next, attempt to use (scrollY / maxScrollY). This will fail if the
  // getMaxScrollX() method is not available.
  if ((scrollY >= 0) && (maxScrollY > 0)) {
    return (scrollY / (float) maxScrollY);
  }

  // Finally, attempt to use (scrollY / itemCount).
  // TODO: Investigate if it is still needed.
  if ((scrollY >= 0) && (itemCount > 0) && (scrollY <= itemCount)) {
    return (scrollY / (float) itemCount);
  }

  return defaultValue;
}
 
Example 11
Source File: TextEventInterpreter.java    From talkback with Apache License 2.0 5 votes vote down vote up
private boolean appendLastWordIfNeeded(
    AccessibilityEvent event, TextEventInterpretation interpretation) {
  final CharSequence text = getEventText(event);
  final CharSequence addedText = getAddedText(event);
  final int fromIndex = event.getFromIndex();

  if (fromIndex > text.length()) {
    LogUtils.w(TAG, "Received event with invalid fromIndex: %s", event);
    return false;
  }

  // Check if any visible text was added.
  if (addedText != null) {
    int trimmedLength = TextUtils.getTrimmedLength(addedText);
    if (trimmedLength > 0) {
      return false;
    }
  }

  final int breakIndex = getPrecedingWhitespace(text, fromIndex);
  final CharSequence word = text.subSequence(breakIndex, fromIndex);

  // Did the user just type a word?
  if (TextUtils.getTrimmedLength(word) == 0) {
    return false;
  }

  interpretation.setInitialWord(word);
  return true;
}
 
Example 12
Source File: TextEventInterpreter.java    From talkback with Apache License 2.0 5 votes vote down vote up
/**
 * Returns {@code null}, empty string or the added text depending on the event.
 *
 * <p>For cases where event.getText() is null or bad size, text interpretation is expected to be
 * set invalid with "addedText is null" in interpretTextChange(). Hence where event.getText() is
 * null or bad size, we return null as returning an empty string here would bypass this condition
 * and the text interpretation would be incorrect.
 *
 * @param event
 * @return the added text.
 */
private static @Nullable CharSequence getAddedText(AccessibilityEvent event) {
  final List<CharSequence> textList = event.getText();
  // noinspection ConstantConditions
  if (textList == null || textList.size() > 1) {
    LogUtils.w(TAG, "getAddedText: Text list was null or bad size");
    return null;
  }

  // If the text was empty, the list will be empty. See the
  // implementation for TextView.onPopulateAccessibilityEvent().
  if (textList.size() == 0) {
    return "";
  }

  final CharSequence text = textList.get(0);
  if (text == null) {
    LogUtils.w(TAG, "getAddedText: First text entry was null");
    return null;
  }

  final int addedBegIndex = event.getFromIndex();
  final int addedEndIndex = addedBegIndex + event.getAddedCount();
  if (areInvalidIndices(text, addedBegIndex, addedEndIndex)) {
    LogUtils.w(
        TAG,
        "getAddedText: Invalid indices (%d,%d) for \"%s\"",
        addedBegIndex,
        addedEndIndex,
        text);
    return "";
  }

  return getSubsequenceWithSpans(text, addedBegIndex, addedEndIndex);
}
 
Example 13
Source File: TextEventInterpreter.java    From talkback with Apache License 2.0 5 votes vote down vote up
private static @Nullable CharSequence getRemovedText(AccessibilityEvent event) {
  final CharSequence beforeText = event.getBeforeText();
  if (beforeText == null) {
    return null;
  }

  final int beforeBegIndex = event.getFromIndex();
  final int beforeEndIndex = beforeBegIndex + event.getRemovedCount();
  if (areInvalidIndices(beforeText, beforeBegIndex, beforeEndIndex)) {
    return "";
  }

  return getSubsequenceWithSpans(beforeText, beforeBegIndex, beforeEndIndex);
}
 
Example 14
Source File: AccessibilityScrollData.java    From appium-uiautomator2-server with Apache License 2.0 5 votes vote down vote up
public AccessibilityScrollData(AccessibilityEvent event) {
    this.scrollX = event.getScrollX();
    this.scrollY = event.getScrollY();
    this.maxScrollX = event.getMaxScrollX();
    this.maxScrollY = event.getMaxScrollY();
    this.fromIndex = event.getFromIndex();
    this.toIndex = event.getToIndex();
    this.itemCount = event.getItemCount();
}
 
Example 15
Source File: LaunchApp.java    From PUMA with Apache License 2.0 5 votes vote down vote up
private ScrollDirection findScrollDirection() {
	AccessibilityNodeInfo localRoot = getRootNode();
	AccessibilityNodeInfo toScroll = getScrollableNode(localRoot);
	ScrollDirection sDir = new ScrollDirection();
	AccessibilityEvent event;
	boolean atBeginning;

	// Util.log(toScroll);

	// Test Up --> Down
	event = scrollAndWaitForLastEvent(toScroll, true, true);
	sDir.vertical = checkScrollEvent(event);

	if (!toScroll.getClassName().equals(ListView.class.getCanonicalName())) {
		// Test Left --> Right
		event = scrollAndWaitForLastEvent(toScroll, false, true);
		sDir.horizontal = checkScrollEvent(event);

		// Restore view position
		do {
			event = scrollAndWaitForLastEvent(toScroll, false, false);
			atBeginning = (event == null) || (event.getFromIndex() == 0) || (event.getScrollX() == 0);
		} while (!atBeginning);
	}

	// Restore view position
	do {
		event = scrollAndWaitForLastEvent(toScroll, true, false);
		atBeginning = (event == null) || (event.getFromIndex() == 0) || (event.getScrollY() == 0);
	} while (!atBeginning);

	return sDir;
}
 
Example 16
Source File: LaunchApp.java    From PUMA with Apache License 2.0 4 votes vote down vote up
private boolean checkScrollEvent(AccessibilityEvent event) {
	return ((event != null) && (event.getFromIndex() > -1 || event.getScrollX() > -1 || event.getScrollY() > -1));
}
 
Example 17
Source File: MyInteractionController.java    From PUMA with Apache License 2.0 4 votes vote down vote up
/**
 * Handle swipes in any direction where the result is a scroll event. This call blocks
 * until the UI has fired a scroll event or timeout.
 * @param downX
 * @param downY
 * @param upX
 * @param upY
 * @param steps
 * @return true if we are not at the beginning or end of the scrollable view.
 */
public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY, final int steps) {
	Log.d(LOG_TAG, "scrollSwipe (" + downX + ", " + downY + ", " + upX + ", " + upY + ", " + steps + ")");

	Runnable command = new Runnable() {
		@Override
		public void run() {
			swipe(downX, downY, upX, upY, steps);
		}
	};

	// Collect all accessibility events generated during the swipe command and get the
	// last event
	ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
	runAndWaitForEvents(command, new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events), Configurator.getInstance().getScrollAcknowledgmentTimeout());

	AccessibilityEvent event = getLastMatchingEvent(events, AccessibilityEvent.TYPE_VIEW_SCROLLED);

	if (event == null) {
		// end of scroll since no new scroll events received
		recycleAccessibilityEvents(events);
		return false;
	}

	// AdapterViews have indices we can use to check for the beginning.
	boolean foundEnd = false;
	if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
		foundEnd = event.getFromIndex() == 0 || (event.getItemCount() - 1) == event.getToIndex();
		Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
	} else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
		// Determine if we are scrolling vertically or horizontally.
		if (downX == upX) {
			// Vertical
			foundEnd = event.getScrollY() == 0 || event.getScrollY() == event.getMaxScrollY();
			Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
		} else if (downY == upY) {
			// Horizontal
			foundEnd = event.getScrollX() == 0 || event.getScrollX() == event.getMaxScrollX();
			Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
		}
	}
	recycleAccessibilityEvents(events);
	return !foundEnd;
}
 
Example 18
Source File: InteractionController.java    From za-Farmer with MIT License 4 votes vote down vote up
/**
 * Handle swipes in any direction where the result is a scroll event. This call blocks
 * until the UI has fired a scroll event or timeout.
 * @param downX
 * @param downY
 * @param upX
 * @param upY
 * @param steps
 * @return true if we are not at the beginning or end of the scrollable view.
 */
public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
        final int steps) {
    Log.d(LOG_TAG, "scrollSwipe (" +  downX + ", " + downY + ", " + upX + ", "
            + upY + ", " + steps +")");

    Runnable command = new Runnable() {
        @Override
        public void run() {
            swipe(downX, downY, upX, upY, steps);
        }
    };

    // Collect all accessibility events generated during the swipe command and get the
    // last event
    ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
    runAndWaitForEvents(command,
            new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events),
            Configurator.getInstance().getScrollAcknowledgmentTimeout());

    AccessibilityEvent event = getLastMatchingEvent(events,
            AccessibilityEvent.TYPE_VIEW_SCROLLED);

    if (event == null) {
        // end of scroll since no new scroll events received
        recycleAccessibilityEvents(events);
        return false;
    }

    // AdapterViews have indices we can use to check for the beginning.
    boolean foundEnd = false;
    if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
        foundEnd = event.getFromIndex() == 0 ||
                (event.getItemCount() - 1) == event.getToIndex();
        Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
    } else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
        // Determine if we are scrolling vertically or horizontally.
        if (downX == upX) {
            // Vertical
            foundEnd = event.getScrollY() == 0 ||
                    event.getScrollY() == event.getMaxScrollY();
            Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
        } else if (downY == upY) {
            // Horizontal
            foundEnd = event.getScrollX() == 0 ||
                    event.getScrollX() == event.getMaxScrollX();
            Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
        }
    }
    recycleAccessibilityEvents(events);
    return !foundEnd;
}
 
Example 19
Source File: InteractionController.java    From JsDroidCmd with Mozilla Public License 2.0 4 votes vote down vote up
/**
 * Handle swipes in any direction where the result is a scroll event. This
 * call blocks until the UI has fired a scroll event or timeout.
 * 
 * @param downX
 * @param downY
 * @param upX
 * @param upY
 * @param steps
 * @return true if we are not at the beginning or end of the scrollable
 *         view.
 */
public boolean scrollSwipe(final int downX, final int downY, final int upX,
		final int upY, final int steps) {
	Runnable command = new Runnable() {
		@Override
		public void run() {
			swipe(downX, downY, upX, upY, steps);
		}
	};

	// Collect all accessibility events generated during the swipe command
	// and get the
	// last event
	ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
	runAndWaitForEvents(command, new EventCollectingPredicate(
			AccessibilityEvent.TYPE_VIEW_SCROLLED, events), Configurator
			.getInstance().getScrollAcknowledgmentTimeout());

	AccessibilityEvent event = getLastMatchingEvent(events,
			AccessibilityEvent.TYPE_VIEW_SCROLLED);

	if (event == null) {
		// end of scroll since no new scroll events received
		recycleAccessibilityEvents(events);
		return false;
	}

	// AdapterViews have indices we can use to check for the beginning.
	boolean foundEnd = false;
	if (event.getFromIndex() != -1 && event.getToIndex() != -1
			&& event.getItemCount() != -1) {
		foundEnd = event.getFromIndex() == 0
				|| (event.getItemCount() - 1) == event.getToIndex();
		Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
	} else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
		// Determine if we are scrolling vertically or horizontally.
		if (downX == upX) {
			// Vertical
			foundEnd = event.getScrollY() == 0
					|| event.getScrollY() == event.getMaxScrollY();
			Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: "
					+ foundEnd);
		} else if (downY == upY) {
			// Horizontal
			foundEnd = event.getScrollX() == 0
					|| event.getScrollX() == event.getMaxScrollX();
			Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: "
					+ foundEnd);
		}
	}
	recycleAccessibilityEvents(events);
	return !foundEnd;
}
 
Example 20
Source File: Until.java    From za-Farmer with MIT License 4 votes vote down vote up
/**
 * Returns a condition that depends on a scroll having reached the end in the given
 * {@code direction}.
 *
 * @param direction The direction of the scroll.
 */
public static EventCondition<Boolean> scrollFinished(final Direction direction) {
    return new EventCondition<Boolean>() {
        private Direction mDirection = direction;
        private Boolean mResult = null;

        @Override
        Boolean apply(AccessibilityEvent event) {
            if (event.getFromIndex() != -1 && event.getToIndex() != -1 &&
                    event.getItemCount() != -1) {

                switch (mDirection) {
                    case UP:
                        mResult = (event.getFromIndex() == 0);
                        break;
                    case DOWN:
                        mResult = (event.getToIndex() == event.getItemCount() - 1);
                        break;
                    case LEFT:
                        mResult = (event.getFromIndex() == 0);
                        break;
                    case RIGHT:
                        mResult = (event.getToIndex() == event.getItemCount() - 1);
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid Direction");
                }
            } else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
                switch (mDirection) {
                    case UP:
                        mResult = (event.getScrollY() == 0);
                        break;
                    case DOWN:
                        mResult = (event.getScrollY() == event.getMaxScrollY());
                        break;
                    case LEFT:
                        mResult = (event.getScrollX() == 0);
                        break;
                    case RIGHT:
                        mResult = (event.getScrollX() == event.getMaxScrollX());
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid Direction");
                }
            }

            // Keep listening for events until the result is set to true (we reached the end)
            return Boolean.TRUE.equals(mResult);
        }

        @Override
        Boolean getResult() {
            // If we didn't recieve any scroll events (mResult == null), assume we're already at
            // the end and return true.
            return mResult == null || mResult;
        }
    };
}