private void track(MotionEvent event) {
    mX.saveTouchPos();
    mY.saveTouchPos();

    for (int i = 0; i < event.getHistorySize(); i++) {
      track(
          event.getHistoricalX(0, i), event.getHistoricalY(0, i), event.getHistoricalEventTime(i));
    }
    track(event.getX(0), event.getY(0), event.getEventTime());

    if (stopped()) {
      if (mState == PanZoomState.PANNING) {
        setState(PanZoomState.PANNING_HOLD);
      } else if (mState == PanZoomState.PANNING_LOCKED) {
        setState(PanZoomState.PANNING_HOLD_LOCKED);
      } else {
        // should never happen, but handle anyway for robustness
        Log.e(LOGTAG, "Impossible case " + mState + " when stopped in track");
        setState(PanZoomState.PANNING_HOLD_LOCKED);
      }
    }

    mX.startPan();
    mY.startPan();
    updatePosition();
  }
  private void fling() {
    updatePosition();

    stopAnimationTimer();

    boolean stopped = stopped();
    mX.startFling(stopped);
    mY.startFling(stopped);

    startAnimationTimer(new FlingRunnable());
  }