public void reload() {
    ConfigurationSection cfg = plugin.getConfig();
    apiKey = plugin.getConfig().getString("api-key");
    debugModeEnabled = cfg.getBoolean("debug");
    String au = cfg.getString("auto-update", "true");
    autoUpdate = AutoUpdate.valueOf(au.toUpperCase().replace("-", "_"));
    if (autoUpdate == null) {
      autoUpdate = AutoUpdate.NOTIFY_ONLY;
    }
    storageBackend = StorageBackend.valueOf(cfg.getString("storage-backend", "file").toUpperCase());
    if (storageBackend == null) {
      storageBackend = StorageBackend.FILE;
    }
    language = cfg.getString("language", "english");
    dataExpiry = periodStringToMillis(cfg.getString("data-expiry", "90d"));
    chestBlockId = cfg.getInt("chest-block", 130);
    selectionWandId = cfg.getInt("selection-wand-id", 294);
    doubleClickInterval = cfg.getInt("double-click-interval", 2000);
    previewDuration = cfg.getInt("preview-duration", 3500);
    carryEffectEnabled = cfg.getBoolean("carry-effect", false);
    carryEffect =
        PotionEffectType.getByName(cfg.getString("carry-effect-type", "slow").toUpperCase());
    buildingProtectionEnabled = cfg.getBoolean("protect-buildings", true);
    containerLockingEnabled = cfg.getBoolean("lock-containers", true);
    buildPermissionCheckEnabled = cfg.getBoolean("check-build-permissions", true);
    preventPlacingEnderchestsEnabled = cfg.getBoolean("prevent-placing-enderchests", false);
    pickupEnabled = cfg.getBoolean("allow-pickup", true);
    lockingEnabled = cfg.getBoolean("allow-locking", true);
    unlockingOthersEnabled = cfg.getBoolean("allow-unlocking-others", true);
    unlockTime = cfg.getInt("unlock-time", 10);
    unlockTimeOwn = cfg.getInt("unlock-time-own", 5);
    lockTime = cfg.getInt("lock-time", 5);
    lockCost = cfg.getInt("lock-cost", 0);
    unlockCost = cfg.getInt("unlock-cost", 0);
    pickupCost = cfg.getInt("pickup-cost", 0);
    buildCost = cfg.getInt("build-cost", 0);
    maxLockingDistance = cfg.getInt("max-locking-distance", 5);
    maxBlocksPerTick = cfg.getInt("max-blocks-per-tick", 500);
    buildAnimationStyle =
        AnimationStyle.valueOf(cfg.getString("build-animation.style", "BREAK").toUpperCase());
    if (buildAnimationStyle == null) {
      buildAnimationStyle = AnimationStyle.NONE;
    }
    buildAnimationShuffled = cfg.getBoolean("build-animation.shuffle", true);
    buildBlocksPerTick = cfg.getInt("build-animation.blocks-per-tick", 5);
    buildFireworks = cfg.getInt("build-animation.fireworks", 3);

    pickupAnimationStyle =
        AnimationStyle.valueOf(cfg.getString("pickup-animation.style", "BREAK").toUpperCase());
    if (pickupAnimationStyle == null) {
      pickupAnimationStyle = AnimationStyle.NONE;
    }
    pickupAnimationShuffled = cfg.getBoolean("pickup-animation.shuffle", true);
    pickupBlocksPerTick = cfg.getInt("pickup-animation.blocks-per-tick", 20);
    pickupFireworks = cfg.getInt("pickup-animation.fireworks", 0);
  }
 protected LoadingLayout createLoadingLayout(Context context, Mode mode, TypedArray attrs) {
   LoadingLayout layout =
       mLoadingAnimationStyle.createLoadingLayout(
           context, mode, getPullToRefreshScrollDirection(), attrs);
   layout.setVisibility(View.INVISIBLE);
   return layout;
 }
public abstract class PullToRefreshBase<T extends View> extends LinearLayout
    implements IPullToRefresh<T> {

  // ===========================================================
  // Constants
  // ===========================================================

  static final boolean DEBUG = true;

  static final boolean USE_HW_LAYERS = false;

  static final String LOG_TAG = "PullToRefresh";

  static final float FRICTION = 2.0f;

  public static final int SMOOTH_SCROLL_DURATION_MS = 200;
  public static final int SMOOTH_SCROLL_LONG_DURATION_MS = 325;
  static final int DEMO_SCROLL_INTERVAL = 225;

  static final String STATE_STATE = "ptr_state";
  static final String STATE_MODE = "ptr_mode";
  static final String STATE_CURRENT_MODE = "ptr_current_mode";
  static final String STATE_SCROLLING_REFRESHING_ENABLED = "ptr_disable_scrolling";
  static final String STATE_SHOW_REFRESHING_VIEW = "ptr_show_refreshing_view";
  static final String STATE_SUPER = "ptr_super";

  // ===========================================================
  // Fields
  // ===========================================================

  private int mTouchSlop;
  private float mLastMotionX, mLastMotionY;
  private float mInitialMotionX, mInitialMotionY;

  private boolean mIsBeingDragged = false;
  private State mState = State.RESET;
  private Mode mMode = Mode.getDefault();

  private Mode mCurrentMode;
  T mRefreshableView;
  private FrameLayout mRefreshableViewWrapper;

  private boolean mShowViewWhileRefreshing = true;
  private boolean mScrollingWhileRefreshingEnabled = false;
  private boolean mFilterTouchEvents = true;
  private boolean mOverScrollEnabled = true;
  private boolean mLayoutVisibilityChangesEnabled = true;

  private Interpolator mScrollAnimationInterpolator;
  private AnimationStyle mLoadingAnimationStyle = AnimationStyle.getDefault();

  private LoadingLayout mHeaderLayout;
  private LoadingLayout mFooterLayout;

  private OnRefreshListener<T> mOnRefreshListener;
  private OnRefreshListener2<T> mOnRefreshListener2;
  private OnPullEventListener<T> mOnPullEventListener;

  private SmoothScrollRunnable mCurrentSmoothScrollRunnable;

  // ===========================================================
  // Constructors
  // ===========================================================

  public PullToRefreshBase(Context context) {
    super(context);
    init(context, null);
  }

  public PullToRefreshBase(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
  }

  public PullToRefreshBase(Context context, Mode mode) {
    super(context);
    mMode = mode;
    init(context, null);
  }

  public PullToRefreshBase(Context context, Mode mode, AnimationStyle animStyle) {
    super(context);
    mMode = mode;
    mLoadingAnimationStyle = animStyle;
    init(context, null);
  }

  @Override
  public void addView(View child, int index, ViewGroup.LayoutParams params) {
    if (DEBUG) {
      Log.d(LOG_TAG, "addView: " + child.getClass().getSimpleName());
    }

    final T refreshableView = getRefreshableView();

    if (refreshableView instanceof ViewGroup) {
      ((ViewGroup) refreshableView).addView(child, index, params);
    } else {
      throw new UnsupportedOperationException(
          "Refreshable View is not a ViewGroup so can't addView");
    }
  }

  @Override
  public final boolean demo() {
    if (mMode.showHeaderLoadingLayout() && isReadyForPullStart()) {
      smoothScrollToAndBack(-getHeaderSize() * 2);
      return true;
    } else if (mMode.showFooterLoadingLayout() && isReadyForPullEnd()) {
      smoothScrollToAndBack(getFooterSize() * 2);
      return true;
    }

    return false;
  }

  @Override
  public final Mode getCurrentMode() {
    return mCurrentMode;
  }

  @Override
  public final boolean getFilterTouchEvents() {
    return mFilterTouchEvents;
  }

  @Override
  public final ILoadingLayout getLoadingLayoutProxy() {
    return getLoadingLayoutProxy(true, true);
  }

  @Override
  public final ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd) {
    return createLoadingLayoutProxy(includeStart, includeEnd);
  }

  @Override
  public final Mode getMode() {
    return mMode;
  }

  @Override
  public final T getRefreshableView() {
    return mRefreshableView;
  }

  @Override
  public final boolean getShowViewWhileRefreshing() {
    return mShowViewWhileRefreshing;
  }

  @Override
  public final State getState() {
    return mState;
  }

  /** @deprecated See {@link #isScrollingWhileRefreshingEnabled()}. */
  public final boolean isDisableScrollingWhileRefreshing() {
    return !isScrollingWhileRefreshingEnabled();
  }

  @Override
  public final boolean isPullToRefreshEnabled() {
    return mMode.permitsPullToRefresh();
  }

  @Override
  public final boolean isPullToRefreshOverScrollEnabled() {
    return VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD
        && mOverScrollEnabled
        && OverscrollHelper.isAndroidOverScrollEnabled(mRefreshableView);
  }

  @Override
  public final boolean isRefreshing() {
    return mState == State.REFRESHING || mState == State.MANUAL_REFRESHING;
  }

  @Override
  public final boolean isScrollingWhileRefreshingEnabled() {
    return mScrollingWhileRefreshingEnabled;
  }

  @Override
  public final boolean onInterceptTouchEvent(MotionEvent event) {

    if (!isPullToRefreshEnabled()) {
      return false;
    }

    final int action = event.getAction();

    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
      mIsBeingDragged = false;
      return false;
    }

    if (action != MotionEvent.ACTION_DOWN && mIsBeingDragged) {
      return true;
    }

    switch (action) {
      case MotionEvent.ACTION_MOVE:
        {
          // If we're refreshing, and the flag is set. Eat all MOVE events
          if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
            return true;
          }

          if (isReadyForPull()) {
            final float y = event.getY(), x = event.getX();
            final float diff, oppositeDiff, absDiff;

            // We need to use the correct values, based on scroll
            // direction
            switch (getPullToRefreshScrollDirection()) {
              case HORIZONTAL:
                diff = x - mLastMotionX;
                oppositeDiff = y - mLastMotionY;
                break;
              case VERTICAL:
              default:
                diff = y - mLastMotionY;
                oppositeDiff = x - mLastMotionX;
                break;
            }
            absDiff = Math.abs(diff);

            if (absDiff > mTouchSlop && (!mFilterTouchEvents || absDiff > Math.abs(oppositeDiff))) {
              if (mMode.showHeaderLoadingLayout() && diff >= 1f && isReadyForPullStart()) {
                mLastMotionY = y;
                mLastMotionX = x;
                mIsBeingDragged = true;
                if (mMode == Mode.BOTH) {
                  mCurrentMode = Mode.PULL_FROM_START;
                }
              } else if (mMode.showFooterLoadingLayout() && diff <= -1f && isReadyForPullEnd()) {
                mLastMotionY = y;
                mLastMotionX = x;
                mIsBeingDragged = true;
                if (mMode == Mode.BOTH) {
                  mCurrentMode = Mode.PULL_FROM_END;
                }
              }
            }
          }
          break;
        }
      case MotionEvent.ACTION_DOWN:
        {
          if (isReadyForPull()) {
            mLastMotionY = mInitialMotionY = event.getY();
            mLastMotionX = mInitialMotionX = event.getX();
            mIsBeingDragged = false;
          }
          break;
        }
    }

    return mIsBeingDragged;
  }

  @Override
  public final void onRefreshComplete() {
    if (isRefreshing()) {
      setState(State.RESET);
    }
  }

  @Override
  public final boolean onTouchEvent(MotionEvent event) {

    if (!isPullToRefreshEnabled()) {
      return false;
    }

    // If we're refreshing, and the flag is set. Eat the event
    if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
      return true;
    }

    if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
      return false;
    }

    switch (event.getAction()) {
      case MotionEvent.ACTION_MOVE:
        {
          if (mIsBeingDragged) {
            mLastMotionY = event.getY();
            mLastMotionX = event.getX();
            pullEvent();
            return true;
          }
          break;
        }

      case MotionEvent.ACTION_DOWN:
        {
          if (isReadyForPull()) {
            mLastMotionY = mInitialMotionY = event.getY();
            mLastMotionX = mInitialMotionX = event.getX();
            return true;
          }
          break;
        }

      case MotionEvent.ACTION_CANCEL:
      case MotionEvent.ACTION_UP:
        {
          if (mIsBeingDragged) {
            mIsBeingDragged = false;

            if (mState == State.RELEASE_TO_REFRESH
                && (null != mOnRefreshListener || null != mOnRefreshListener2)) {
              setState(State.REFRESHING, true);
              return true;
            }

            // If we're already refreshing, just scroll back to the top
            if (isRefreshing()) {
              smoothScrollTo(0);
              return true;
            }

            // If we haven't returned by here, then we're not in a state
            // to pull, so just reset
            setState(State.RESET);

            return true;
          }
          break;
        }
    }

    return false;
  }

  public final void setScrollingWhileRefreshingEnabled(boolean allowScrollingWhileRefreshing) {
    mScrollingWhileRefreshingEnabled = allowScrollingWhileRefreshing;
  }

  /** @deprecated See {@link #setScrollingWhileRefreshingEnabled(boolean)} */
  public void setDisableScrollingWhileRefreshing(boolean disableScrollingWhileRefreshing) {
    setScrollingWhileRefreshingEnabled(!disableScrollingWhileRefreshing);
  }

  @Override
  public final void setFilterTouchEvents(boolean filterEvents) {
    mFilterTouchEvents = filterEvents;
  }

  /**
   * @deprecated You should now call this method on the result of {@link #getLoadingLayoutProxy()}.
   */
  public void setLastUpdatedLabel(CharSequence label) {
    getLoadingLayoutProxy().setLastUpdatedLabel(label);
  }

  /**
   * @deprecated You should now call this method on the result of {@link #getLoadingLayoutProxy()}.
   */
  public void setLoadingDrawable(Drawable drawable) {
    getLoadingLayoutProxy().setLoadingDrawable(drawable);
  }

  /**
   * @deprecated You should now call this method on the result of {@link
   *     #getLoadingLayoutProxy(boolean, boolean)}.
   */
  public void setLoadingDrawable(Drawable drawable, Mode mode) {
    getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
        .setLoadingDrawable(drawable);
  }

  @Override
  public void setLongClickable(boolean longClickable) {
    getRefreshableView().setLongClickable(longClickable);
  }

  @Override
  public final void setMode(Mode mode) {
    if (mode != mMode) {
      if (DEBUG) {
        Log.d(LOG_TAG, "Setting mode to: " + mode);
      }
      mMode = mode;
      updateUIForMode();
    }
  }

  public void setOnPullEventListener(OnPullEventListener<T> listener) {
    mOnPullEventListener = listener;
  }

  @Override
  public final void setOnRefreshListener(OnRefreshListener<T> listener) {
    mOnRefreshListener = listener;
    mOnRefreshListener2 = null;
  }

  @Override
  public final void setOnRefreshListener(OnRefreshListener2<T> listener) {
    mOnRefreshListener2 = listener;
    mOnRefreshListener = null;
  }

  /**
   * @deprecated You should now call this method on the result of {@link #getLoadingLayoutProxy()}.
   */
  public void setPullLabel(CharSequence pullLabel) {
    getLoadingLayoutProxy().setPullLabel(pullLabel);
  }

  /**
   * @deprecated You should now call this method on the result of {@link
   *     #getLoadingLayoutProxy(boolean, boolean)}.
   */
  public void setPullLabel(CharSequence pullLabel, Mode mode) {
    getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
        .setPullLabel(pullLabel);
  }

  /**
   * @param enable Whether Pull-To-Refresh should be used
   * @deprecated This simple calls setMode with an appropriate mode based on the passed value.
   */
  public final void setPullToRefreshEnabled(boolean enable) {
    setMode(enable ? Mode.getDefault() : Mode.DISABLED);
  }

  @Override
  public final void setPullToRefreshOverScrollEnabled(boolean enabled) {
    mOverScrollEnabled = enabled;
  }

  @Override
  public final void setRefreshing() {
    setRefreshing(true);
  }

  @Override
  public final void setRefreshing(boolean doScroll) {
    if (!isRefreshing()) {
      setState(State.MANUAL_REFRESHING, doScroll);
    }
  }

  /**
   * @deprecated You should now call this method on the result of {@link #getLoadingLayoutProxy()}.
   */
  public void setRefreshingLabel(CharSequence refreshingLabel) {
    getLoadingLayoutProxy().setRefreshingLabel(refreshingLabel);
  }

  /**
   * @deprecated You should now call this method on the result of {@link
   *     #getLoadingLayoutProxy(boolean, boolean)}.
   */
  public void setRefreshingLabel(CharSequence refreshingLabel, Mode mode) {
    getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
        .setRefreshingLabel(refreshingLabel);
  }

  /**
   * @deprecated You should now call this method on the result of {@link #getLoadingLayoutProxy()}.
   */
  public void setReleaseLabel(CharSequence releaseLabel) {
    setReleaseLabel(releaseLabel, Mode.BOTH);
  }

  /**
   * @deprecated You should now call this method on the result of {@link
   *     #getLoadingLayoutProxy(boolean, boolean)}.
   */
  public void setReleaseLabel(CharSequence releaseLabel, Mode mode) {
    getLoadingLayoutProxy(mode.showHeaderLoadingLayout(), mode.showFooterLoadingLayout())
        .setReleaseLabel(releaseLabel);
  }

  public void setScrollAnimationInterpolator(Interpolator interpolator) {
    mScrollAnimationInterpolator = interpolator;
  }

  @Override
  public final void setShowViewWhileRefreshing(boolean showView) {
    mShowViewWhileRefreshing = showView;
  }

  /**
   * @return Either {@link
   *     com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation#VERTICAL} or {@link
   *     com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation#HORIZONTAL} depending on
   *     the scroll direction.
   */
  public abstract Orientation getPullToRefreshScrollDirection();

  final void setState(State state, final boolean... params) {
    mState = state;
    if (DEBUG) {
      Log.d(LOG_TAG, "State: " + mState.name());
    }

    switch (mState) {
      case RESET:
        onReset();
        break;
      case PULL_TO_REFRESH:
        onPullToRefresh();
        break;
      case RELEASE_TO_REFRESH:
        onReleaseToRefresh();
        break;
      case REFRESHING:
      case MANUAL_REFRESHING:
        onRefreshing(params[0]);
        break;
      case OVERSCROLLING:
        // NO-OP
        break;
    }

    // Call OnPullEventListener
    if (null != mOnPullEventListener) {
      mOnPullEventListener.onPullEvent(this, mState, mCurrentMode);
    }
  }

  /**
   * Used internally for adding view. Need because we override addView to pass-through to the
   * Refreshable View
   */
  protected final void addViewInternal(View child, int index, ViewGroup.LayoutParams params) {
    super.addView(child, index, params);
  }

  /**
   * Used internally for adding view. Need because we override addView to pass-through to the
   * Refreshable View
   */
  protected final void addViewInternal(View child, ViewGroup.LayoutParams params) {
    super.addView(child, -1, params);
  }

  protected LoadingLayout createLoadingLayout(Context context, Mode mode, TypedArray attrs) {
    LoadingLayout layout =
        mLoadingAnimationStyle.createLoadingLayout(
            context, mode, getPullToRefreshScrollDirection(), attrs);
    layout.setVisibility(View.INVISIBLE);
    return layout;
  }

  /**
   * Used internally for {@link #getLoadingLayoutProxy(boolean, boolean)}. Allows derivative classes
   * to include any extra LoadingLayouts.
   */
  protected LoadingLayoutProxy createLoadingLayoutProxy(
      final boolean includeStart, final boolean includeEnd) {
    LoadingLayoutProxy proxy = new LoadingLayoutProxy();

    if (includeStart && mMode.showHeaderLoadingLayout()) {
      proxy.addLayout(mHeaderLayout);
    }
    if (includeEnd && mMode.showFooterLoadingLayout()) {
      proxy.addLayout(mFooterLayout);
    }

    return proxy;
  }

  /**
   * This is implemented by derived classes to return the created View. If you need to use a custom
   * View (such as a custom ListView), override this method and return an instance of your custom
   * class.
   *
   * <p>Be sure to set the ID of the view in this method, especially if you're using a ListActivity
   * or ListFragment.
   *
   * @param context Context to create view with
   * @param attrs AttributeSet from wrapped class. Means that anything you include in the XML layout
   *     declaration will be routed to the created View
   * @return New instance of the Refreshable View
   */
  protected abstract T createRefreshableView(Context context, AttributeSet attrs);

  protected final void disableLoadingLayoutVisibilityChanges() {
    mLayoutVisibilityChangesEnabled = false;
  }

  protected final LoadingLayout getFooterLayout() {
    return mFooterLayout;
  }

  protected final int getFooterSize() {
    return mFooterLayout.getContentSize();
  }

  protected final LoadingLayout getHeaderLayout() {
    return mHeaderLayout;
  }

  protected final int getHeaderSize() {
    return mHeaderLayout.getContentSize();
  }

  protected int getPullToRefreshScrollDuration() {
    return SMOOTH_SCROLL_DURATION_MS;
  }

  protected int getPullToRefreshScrollDurationLonger() {
    return SMOOTH_SCROLL_LONG_DURATION_MS;
  }

  protected FrameLayout getRefreshableViewWrapper() {
    return mRefreshableViewWrapper;
  }

  /**
   * Allows Derivative classes to handle the XML Attrs without creating a TypedArray themsevles
   *
   * @param a - TypedArray of PullToRefresh Attributes
   */
  protected void handleStyledAttributes(TypedArray a) {}

  /**
   * Implemented by derived class to return whether the View is in a state where the user can Pull
   * to Refresh by scrolling from the end.
   *
   * @return true if the View is currently in the correct state (for example, bottom of a ListView)
   */
  protected abstract boolean isReadyForPullEnd();

  /**
   * Implemented by derived class to return whether the View is in a state where the user can Pull
   * to Refresh by scrolling from the start.
   *
   * @return true if the View is currently the correct state (for example, top of a ListView)
   */
  protected abstract boolean isReadyForPullStart();

  /**
   * Called by {@link #onRestoreInstanceState(android.os.Parcelable)} so that derivative classes can
   * handle their saved instance state.
   *
   * @param savedInstanceState - Bundle which contains saved instance state.
   */
  protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {}

  /**
   * Called by {@link #onSaveInstanceState()} so that derivative classes can save their instance
   * state.
   *
   * @param saveState - Bundle to be updated with saved state.
   */
  protected void onPtrSaveInstanceState(Bundle saveState) {}

  /**
   * Called when the UI has been to be updated to be in the {@link
   * com.handmark.pulltorefresh.library.PullToRefreshBase.State#PULL_TO_REFRESH} state.
   */
  protected void onPullToRefresh() {
    switch (mCurrentMode) {
      case PULL_FROM_END:
        mFooterLayout.pullToRefresh();
        break;
      case PULL_FROM_START:
        mHeaderLayout.pullToRefresh();
        break;
      default:
        // NO-OP
        break;
    }
  }

  /**
   * Called when the UI has been to be updated to be in the {@link
   * com.handmark.pulltorefresh.library.PullToRefreshBase.State#REFRESHING} or {@link
   * com.handmark.pulltorefresh.library.PullToRefreshBase.State#MANUAL_REFRESHING} state.
   *
   * @param doScroll - Whether the UI should scroll for this event.
   */
  protected void onRefreshing(final boolean doScroll) {
    if (mMode.showHeaderLoadingLayout()) {
      mHeaderLayout.refreshing();
    }
    if (mMode.showFooterLoadingLayout()) {
      mFooterLayout.refreshing();
    }

    if (doScroll) {
      if (mShowViewWhileRefreshing) {

        // Call Refresh Listener when the Scroll has finished
        OnSmoothScrollFinishedListener listener =
            new OnSmoothScrollFinishedListener() {
              @Override
              public void onSmoothScrollFinished() {
                callRefreshListener();
              }
            };

        switch (mCurrentMode) {
          case MANUAL_REFRESH_ONLY:
          case PULL_FROM_END:
            smoothScrollTo(getFooterSize(), listener);
            break;
          default:
          case PULL_FROM_START:
            smoothScrollTo(-getHeaderSize(), listener);
            break;
        }
      } else {
        smoothScrollTo(0);
      }
    } else {
      // We're not scrolling, so just call Refresh Listener now
      callRefreshListener();
    }
  }

  /**
   * Called when the UI has been to be updated to be in the {@link
   * com.handmark.pulltorefresh.library.PullToRefreshBase.State#RELEASE_TO_REFRESH} state.
   */
  protected void onReleaseToRefresh() {
    switch (mCurrentMode) {
      case PULL_FROM_END:
        mFooterLayout.releaseToRefresh();
        break;
      case PULL_FROM_START:
        mHeaderLayout.releaseToRefresh();
        break;
      default:
        // NO-OP
        break;
    }
  }

  /**
   * Called when the UI has been to be updated to be in the {@link
   * com.handmark.pulltorefresh.library.PullToRefreshBase.State#RESET} state.
   */
  protected void onReset() {
    mIsBeingDragged = false;
    mLayoutVisibilityChangesEnabled = true;

    // Always reset both layouts, just in case...
    mHeaderLayout.reset();
    mFooterLayout.reset();

    smoothScrollTo(0);
  }

  @Override
  protected final void onRestoreInstanceState(Parcelable state) {
    if (state instanceof Bundle) {
      Bundle bundle = (Bundle) state;

      setMode(Mode.mapIntToValue(bundle.getInt(STATE_MODE, 0)));
      mCurrentMode = Mode.mapIntToValue(bundle.getInt(STATE_CURRENT_MODE, 0));

      mScrollingWhileRefreshingEnabled =
          bundle.getBoolean(STATE_SCROLLING_REFRESHING_ENABLED, false);
      mShowViewWhileRefreshing = bundle.getBoolean(STATE_SHOW_REFRESHING_VIEW, true);

      // Let super Restore Itself
      super.onRestoreInstanceState(bundle.getParcelable(STATE_SUPER));

      State viewState = State.mapIntToValue(bundle.getInt(STATE_STATE, 0));
      if (viewState == State.REFRESHING || viewState == State.MANUAL_REFRESHING) {
        setState(viewState, true);
      }

      // Now let derivative classes restore their state
      onPtrRestoreInstanceState(bundle);
      return;
    }

    super.onRestoreInstanceState(state);
  }

  @Override
  protected final Parcelable onSaveInstanceState() {
    Bundle bundle = new Bundle();

    // Let derivative classes get a chance to save state first, that way we
    // can make sure they don't overrite any of our values
    onPtrSaveInstanceState(bundle);

    bundle.putInt(STATE_STATE, mState.getIntValue());
    bundle.putInt(STATE_MODE, mMode.getIntValue());
    bundle.putInt(STATE_CURRENT_MODE, mCurrentMode.getIntValue());
    bundle.putBoolean(STATE_SCROLLING_REFRESHING_ENABLED, mScrollingWhileRefreshingEnabled);
    bundle.putBoolean(STATE_SHOW_REFRESHING_VIEW, mShowViewWhileRefreshing);
    bundle.putParcelable(STATE_SUPER, super.onSaveInstanceState());

    return bundle;
  }

  @Override
  protected final void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (DEBUG) {
      Log.d(LOG_TAG, String.format("onSizeChanged. W: %d, H: %d", w, h));
    }

    super.onSizeChanged(w, h, oldw, oldh);

    // We need to update the header/footer when our size changes
    refreshLoadingViewsSize();

    // Update the Refreshable View layout
    refreshRefreshableViewSize(w, h);

    /**
     * As we're currently in a Layout Pass, we need to schedule another one to layout any changes
     * we've made here
     */
    post(
        new Runnable() {
          @Override
          public void run() {
            requestLayout();
          }
        });
  }

  /** Re-measure the Loading Views height, and adjust internal padding as necessary */
  protected final void refreshLoadingViewsSize() {
    final int maximumPullScroll = (int) (getMaximumPullScroll() * 1.2f);

    int pLeft = getPaddingLeft();
    int pTop = getPaddingTop();
    int pRight = getPaddingRight();
    int pBottom = getPaddingBottom();

    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        if (mMode.showHeaderLoadingLayout()) {
          mHeaderLayout.setWidth(maximumPullScroll);
          pLeft = -maximumPullScroll;
        } else {
          pLeft = 0;
        }

        if (mMode.showFooterLoadingLayout()) {
          mFooterLayout.setWidth(maximumPullScroll);
          pRight = -maximumPullScroll;
        } else {
          pRight = 0;
        }
        break;

      case VERTICAL:
        if (mMode.showHeaderLoadingLayout()) {
          mHeaderLayout.setHeight(maximumPullScroll);
          pTop = -maximumPullScroll;
        } else {
          pTop = 0;
        }

        if (mMode.showFooterLoadingLayout()) {
          mFooterLayout.setHeight(maximumPullScroll);
          pBottom = -maximumPullScroll;
        } else {
          pBottom = 0;
        }
        break;
    }

    if (DEBUG) {
      Log.d(
          LOG_TAG,
          String.format(
              "Setting Padding. L: %d, T: %d, R: %d, B: %d", pLeft, pTop, pRight, pBottom));
    }
    setPadding(pLeft, pTop, pRight, pBottom);
  }

  protected final void refreshRefreshableViewSize(int width, int height) {
    // We need to set the Height of the Refreshable View to the same as
    // this layout
    LayoutParams lp = (LayoutParams) mRefreshableViewWrapper.getLayoutParams();

    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        if (lp.width != width) {
          lp.width = width;
          mRefreshableViewWrapper.requestLayout();
        }
        break;
      case VERTICAL:
        if (lp.height != height) {
          lp.height = height;
          mRefreshableViewWrapper.requestLayout();
        }
        break;
    }
  }

  /**
   * Helper method which just calls scrollTo() in the correct scrolling direction.
   *
   * @param value - New Scroll value
   */
  protected final void setHeaderScroll(int value) {
    if (DEBUG) {
      Log.d(LOG_TAG, "setHeaderScroll: " + value);
    }

    // Clamp value to with pull scroll range
    final int maximumPullScroll = getMaximumPullScroll();
    value = Math.min(maximumPullScroll, Math.max(-maximumPullScroll, value));

    if (mLayoutVisibilityChangesEnabled) {
      if (value < 0) {
        mHeaderLayout.setVisibility(View.VISIBLE);
      } else if (value > 0) {
        mFooterLayout.setVisibility(View.VISIBLE);
      } else {
        mHeaderLayout.setVisibility(View.INVISIBLE);
        mFooterLayout.setVisibility(View.INVISIBLE);
      }
    }

    if (USE_HW_LAYERS) {
      /**
       * Use a Hardware Layer on the Refreshable View if we've scrolled at all. We don't use them on
       * the Header/Footer Views as they change often, which would negate any HW layer performance
       * boost.
       */
      ViewCompat.setLayerType(
          mRefreshableViewWrapper, value != 0 ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE);
    }

    switch (getPullToRefreshScrollDirection()) {
      case VERTICAL:
        scrollTo(0, value);
        break;
      case HORIZONTAL:
        scrollTo(value, 0);
        break;
    }
  }

  /**
   * Smooth Scroll to position using the default duration of {@value #SMOOTH_SCROLL_DURATION_MS} ms.
   *
   * @param scrollValue - Position to scroll to
   */
  protected final void smoothScrollTo(int scrollValue) {
    smoothScrollTo(scrollValue, getPullToRefreshScrollDuration());
  }

  /**
   * Smooth Scroll to position using the default duration of {@value #SMOOTH_SCROLL_DURATION_MS} ms.
   *
   * @param scrollValue - Position to scroll to
   * @param listener - Listener for scroll
   */
  protected final void smoothScrollTo(int scrollValue, OnSmoothScrollFinishedListener listener) {
    smoothScrollTo(scrollValue, getPullToRefreshScrollDuration(), 0, listener);
  }

  /**
   * Smooth Scroll to position using the longer default duration of {@value
   * #SMOOTH_SCROLL_LONG_DURATION_MS} ms.
   *
   * @param scrollValue - Position to scroll to
   */
  protected final void smoothScrollToLonger(int scrollValue) {
    smoothScrollTo(scrollValue, getPullToRefreshScrollDurationLonger());
  }

  /**
   * Updates the View State when the mode has been set. This does not do any checking that the mode
   * is different to current state so always updates.
   */
  protected void updateUIForMode() {
    // We need to use the correct LayoutParam values, based on scroll
    // direction
    final LayoutParams lp = getLoadingLayoutLayoutParams();

    // Remove Header, and then add Header Loading View again if needed
    if (this == mHeaderLayout.getParent()) {
      removeView(mHeaderLayout);
    }
    if (mMode.showHeaderLoadingLayout()) {
      addViewInternal(mHeaderLayout, 0, lp);
    }

    // Remove Footer, and then add Footer Loading View again if needed
    if (this == mFooterLayout.getParent()) {
      removeView(mFooterLayout);
    }
    if (mMode.showFooterLoadingLayout()) {
      addViewInternal(mFooterLayout, lp);
    }

    // Hide Loading Views
    refreshLoadingViewsSize();

    // If we're not using Mode.BOTH, set mCurrentMode to mMode, otherwise
    // set it to pull down
    mCurrentMode = (mMode != Mode.BOTH) ? mMode : Mode.PULL_FROM_START;
  }

  private void addRefreshableView(Context context, T refreshableView) {
    mRefreshableViewWrapper = new FrameLayout(context);
    mRefreshableViewWrapper.addView(
        refreshableView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

    addViewInternal(
        mRefreshableViewWrapper,
        new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
  }

  private void callRefreshListener() {
    if (null != mOnRefreshListener) {
      mOnRefreshListener.onRefresh(this);
    } else if (null != mOnRefreshListener2) {
      if (mCurrentMode == Mode.PULL_FROM_START) {
        mOnRefreshListener2.onPullDownToRefresh(this);
      } else if (mCurrentMode == Mode.PULL_FROM_END) {
        mOnRefreshListener2.onPullUpToRefresh(this);
      }
    }
  }

  @SuppressWarnings("deprecation")
  private void init(Context context, AttributeSet attrs) {
    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        setOrientation(LinearLayout.HORIZONTAL);
        break;
      case VERTICAL:
      default:
        setOrientation(LinearLayout.VERTICAL);
        break;
    }

    setGravity(Gravity.CENTER);

    ViewConfiguration config = ViewConfiguration.get(context);
    mTouchSlop = config.getScaledTouchSlop();

    // Styleables from XML
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullToRefresh);

    if (a.hasValue(R.styleable.PullToRefresh_ptrMode)) {
      mMode = Mode.mapIntToValue(a.getInteger(R.styleable.PullToRefresh_ptrMode, 0));
    }

    if (a.hasValue(R.styleable.PullToRefresh_ptrAnimationStyle)) {
      mLoadingAnimationStyle =
          AnimationStyle.mapIntToValue(
              a.getInteger(R.styleable.PullToRefresh_ptrAnimationStyle, 0));
    }

    // Refreshable View
    // By passing the attrs, we can add ListView/GridView params via XML
    mRefreshableView = createRefreshableView(context, attrs);
    addRefreshableView(context, mRefreshableView);

    // We need to create now layouts now
    mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
    mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);

    /** Styleables from XML */
    if (a.hasValue(R.styleable.PullToRefresh_ptrRefreshableViewBackground)) {
      Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrRefreshableViewBackground);
      if (null != background) {
        mRefreshableView.setBackgroundDrawable(background);
      }
    } else if (a.hasValue(R.styleable.PullToRefresh_ptrAdapterViewBackground)) {
      Utils.warnDeprecation("ptrAdapterViewBackground", "ptrRefreshableViewBackground");
      Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrAdapterViewBackground);
      if (null != background) {
        mRefreshableView.setBackgroundDrawable(background);
      }
    }

    if (a.hasValue(R.styleable.PullToRefresh_ptrOverScroll)) {
      mOverScrollEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrOverScroll, true);
    }

    if (a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
      mScrollingWhileRefreshingEnabled =
          a.getBoolean(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled, false);
    }

    // Let the derivative classes have a go at handling attributes, then
    // recycle them...
    handleStyledAttributes(a);
    a.recycle();

    // Finally update the UI for the modes
    updateUIForMode();
  }

  private boolean isReadyForPull() {
    switch (mMode) {
      case PULL_FROM_START:
        return isReadyForPullStart();
      case PULL_FROM_END:
        return isReadyForPullEnd();
      case BOTH:
        return isReadyForPullEnd() || isReadyForPullStart();
      default:
        return false;
    }
  }

  /**
   * Actions a Pull Event
   *
   * @return true if the Event has been handled, false if there has been no change
   */
  private void pullEvent() {
    final int newScrollValue;
    final int itemDimension;
    final float initialMotionValue, lastMotionValue;

    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        initialMotionValue = mInitialMotionX;
        lastMotionValue = mLastMotionX;
        break;
      case VERTICAL:
      default:
        initialMotionValue = mInitialMotionY;
        lastMotionValue = mLastMotionY;
        break;
    }

    switch (mCurrentMode) {
      case PULL_FROM_END:
        newScrollValue = Math.round(Math.max(initialMotionValue - lastMotionValue, 0) / FRICTION);
        itemDimension = getFooterSize();
        break;
      case PULL_FROM_START:
      default:
        newScrollValue = Math.round(Math.min(initialMotionValue - lastMotionValue, 0) / FRICTION);
        itemDimension = getHeaderSize();
        break;
    }

    setHeaderScroll(newScrollValue);

    if (newScrollValue != 0 && !isRefreshing()) {
      float scale = Math.abs(newScrollValue) / (float) itemDimension;
      switch (mCurrentMode) {
        case PULL_FROM_END:
          mFooterLayout.onPull(scale);
          break;
        case PULL_FROM_START:
        default:
          mHeaderLayout.onPull(scale);
          break;
      }

      if (mState != State.PULL_TO_REFRESH && itemDimension >= Math.abs(newScrollValue)) {
        setState(State.PULL_TO_REFRESH);
      } else if (mState == State.PULL_TO_REFRESH && itemDimension < Math.abs(newScrollValue)) {
        setState(State.RELEASE_TO_REFRESH);
      }
    }
  }

  private LayoutParams getLoadingLayoutLayoutParams() {
    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
      case VERTICAL:
      default:
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }
  }

  private int getMaximumPullScroll() {
    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        return Math.round(getWidth() / FRICTION);
      case VERTICAL:
      default:
        return Math.round(getHeight() / FRICTION);
    }
  }

  /**
   * Smooth Scroll to position using the specific duration
   *
   * @param scrollValue - Position to scroll to
   * @param duration - Duration of animation in milliseconds
   */
  private final void smoothScrollTo(int scrollValue, long duration) {
    smoothScrollTo(scrollValue, duration, 0, null);
  }

  private final void smoothScrollTo(
      int newScrollValue,
      long duration,
      long delayMillis,
      OnSmoothScrollFinishedListener listener) {
    if (null != mCurrentSmoothScrollRunnable) {
      mCurrentSmoothScrollRunnable.stop();
    }

    final int oldScrollValue;
    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        oldScrollValue = getScrollX();
        break;
      case VERTICAL:
      default:
        oldScrollValue = getScrollY();
        break;
    }

    if (oldScrollValue != newScrollValue) {
      if (null == mScrollAnimationInterpolator) {
        // Default interpolator is a Decelerate Interpolator
        mScrollAnimationInterpolator = new DecelerateInterpolator();
      }
      mCurrentSmoothScrollRunnable =
          new SmoothScrollRunnable(oldScrollValue, newScrollValue, duration, listener);

      if (delayMillis > 0) {
        postDelayed(mCurrentSmoothScrollRunnable, delayMillis);
      } else {
        post(mCurrentSmoothScrollRunnable);
      }
    }
  }

  private final void smoothScrollToAndBack(int y) {
    smoothScrollTo(
        y,
        SMOOTH_SCROLL_DURATION_MS,
        0,
        new OnSmoothScrollFinishedListener() {

          @Override
          public void onSmoothScrollFinished() {
            smoothScrollTo(0, SMOOTH_SCROLL_DURATION_MS, DEMO_SCROLL_INTERVAL, null);
          }
        });
  }

  public static enum AnimationStyle {
    /**
     * This is the default for Android-PullToRefresh. Allows you to use any drawable, which is
     * automatically rotated and used as a Progress Bar.
     */
    ROTATE,

    /**
     * This is the old default, and what is commonly used on iOS. Uses an arrow image which flips
     * depending on where the user has scrolled.
     */
    FLIP;

    static AnimationStyle getDefault() {
      return ROTATE;
    }

    /**
     * Maps an int to a specific mode. This is needed when saving state, or inflating the view from
     * XML where the mode is given through a attr int.
     *
     * @param modeInt - int to map a Mode to
     * @return Mode that modeInt maps to, or ROTATE by default.
     */
    static AnimationStyle mapIntToValue(int modeInt) {
      switch (modeInt) {
        case 0x0:
        default:
          return ROTATE;
        case 0x1:
          return FLIP;
      }
    }

    LoadingLayout createLoadingLayout(
        Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) {
      switch (this) {
        case ROTATE:
        default:
          return new RotateLoadingLayout(context, mode, scrollDirection, attrs);
        case FLIP:
          return new FlipLoadingLayout(context, mode, scrollDirection, attrs);
      }
    }
  }

  public static enum Mode {

    /** Disable all Pull-to-Refresh gesture and Refreshing handling */
    DISABLED(0x0),

    /**
     * Only allow the user to Pull from the start of the Refreshable View to refresh. The start is
     * either the Top or Left, depending on the scrolling direction.
     */
    PULL_FROM_START(0x1),

    /**
     * Only allow the user to Pull from the end of the Refreshable View to refresh. The start is
     * either the Bottom or Right, depending on the scrolling direction.
     */
    PULL_FROM_END(0x2),

    /** Allow the user to both Pull from the start, from the end to refresh. */
    BOTH(0x3),

    /**
     * Disables Pull-to-Refresh gesture handling, but allows manually setting the Refresh state via
     * {@link com.handmark.pulltorefresh.library.PullToRefreshBase#setRefreshing() setRefreshing()}.
     */
    MANUAL_REFRESH_ONLY(0x4);

    /** @deprecated Use {@link #PULL_FROM_START} from now on. */
    public static Mode PULL_DOWN_TO_REFRESH = Mode.PULL_FROM_START;

    /** @deprecated Use {@link #PULL_FROM_END} from now on. */
    public static Mode PULL_UP_TO_REFRESH = Mode.PULL_FROM_END;

    /**
     * Maps an int to a specific mode. This is needed when saving state, or inflating the view from
     * XML where the mode is given through a attr int.
     *
     * @param modeInt - int to map a Mode to
     * @return Mode that modeInt maps to, or PULL_FROM_START by default.
     */
    static Mode mapIntToValue(final int modeInt) {
      for (Mode value : Mode.values()) {
        if (modeInt == value.getIntValue()) {
          return value;
        }
      }

      // If not, return default
      return getDefault();
    }

    static Mode getDefault() {
      return PULL_FROM_START;
    }

    private int mIntValue;

    // The modeInt values need to match those from attrs.xml
    Mode(int modeInt) {
      mIntValue = modeInt;
    }

    /** @return true if the mode permits Pull-to-Refresh */
    boolean permitsPullToRefresh() {
      return !(this == DISABLED || this == MANUAL_REFRESH_ONLY);
    }

    /** @return true if this mode wants the Loading Layout Header to be shown */
    public boolean showHeaderLoadingLayout() {
      return this == PULL_FROM_START || this == BOTH;
    }

    /** @return true if this mode wants the Loading Layout Footer to be shown */
    public boolean showFooterLoadingLayout() {
      return this == PULL_FROM_END || this == BOTH || this == MANUAL_REFRESH_ONLY;
    }

    int getIntValue() {
      return mIntValue;
    }
  }

  // ===========================================================
  // Inner, Anonymous Classes, and Enumerations
  // ===========================================================

  /**
   * Simple Listener that allows you to be notified when the user has scrolled to the end of the
   * AdapterView. See ( {@link
   * com.handmark.pulltorefresh.library.PullToRefreshAdapterViewBase#setOnLastItemVisibleListener}.
   *
   * @author Chris Banes
   */
  public static interface OnLastItemVisibleListener {

    /** Called when the user has scrolled to the end of the list */
    public void onLastItemVisible();
  }

  /**
   * Listener that allows you to be notified when the user has started or finished a touch event.
   * Useful when you want to append extra UI events (such as sounds). See ( {@link
   * com.handmark.pulltorefresh.library.PullToRefreshAdapterViewBase#setOnPullEventListener}.
   *
   * @author Chris Banes
   */
  public static interface OnPullEventListener<V extends View> {

    /**
     * Called when the internal state has been changed, usually by the user pulling.
     *
     * @param refreshView - View which has had it's state change.
     * @param state - The new state of View.
     * @param direction - One of {@link
     *     com.handmark.pulltorefresh.library.PullToRefreshBase.Mode#PULL_FROM_START} or {@link
     *     com.handmark.pulltorefresh.library.PullToRefreshBase.Mode#PULL_FROM_END} depending on
     *     which direction the user is pulling. Only useful when <var>state</var> is {@link
     *     com.handmark.pulltorefresh.library.PullToRefreshBase.State#PULL_TO_REFRESH} or {@link
     *     com.handmark.pulltorefresh.library.PullToRefreshBase.State#RELEASE_TO_REFRESH}.
     */
    public void onPullEvent(final PullToRefreshBase<V> refreshView, State state, Mode direction);
  }

  /**
   * Simple Listener to listen for any callbacks to Refresh.
   *
   * @author Chris Banes
   */
  public static interface OnRefreshListener<V extends View> {

    /** onRefresh will be called for both a Pull from start, and Pull from end */
    public void onRefresh(final PullToRefreshBase<V> refreshView);
  }

  /**
   * An advanced version of the Listener to listen for callbacks to Refresh. This listener is
   * different as it allows you to differentiate between Pull Ups, and Pull Downs.
   *
   * @author Chris Banes
   */
  public static interface OnRefreshListener2<V extends View> {
    // TODO These methods need renaming to START/END rather than DOWN/UP

    /**
     * onPullDownToRefresh will be called only when the user has Pulled from the start, and
     * released.
     */
    public void onPullDownToRefresh(final PullToRefreshBase<V> refreshView);

    /**
     * onPullUpToRefresh will be called only when the user has Pulled from the end, and released.
     */
    public void onPullUpToRefresh(final PullToRefreshBase<V> refreshView);
  }

  public static enum Orientation {
    VERTICAL,
    HORIZONTAL;
  }

  public static enum State {

    /**
     * When the UI is in a state which means that user is not interacting with the Pull-to-Refresh
     * function.
     */
    RESET(0x0),

    /**
     * When the UI is being pulled by the user, but has not been pulled far enough so that it
     * refreshes when released.
     */
    PULL_TO_REFRESH(0x1),

    /**
     * When the UI is being pulled by the user, and <strong>has</strong> been pulled far enough so
     * that it will refresh when released.
     */
    RELEASE_TO_REFRESH(0x2),

    /** When the UI is currently refreshing, caused by a pull gesture. */
    REFRESHING(0x8),

    /**
     * When the UI is currently refreshing, caused by a call to {@link
     * com.handmark.pulltorefresh.library.PullToRefreshBase#setRefreshing() setRefreshing()}.
     */
    MANUAL_REFRESHING(0x9),

    /** When the UI is currently overscrolling, caused by a fling on the Refreshable View. */
    OVERSCROLLING(0x10);

    /**
     * Maps an int to a specific state. This is needed when saving state.
     *
     * @param stateInt - int to map a State to
     * @return State that stateInt maps to
     */
    static State mapIntToValue(final int stateInt) {
      for (State value : State.values()) {
        if (stateInt == value.getIntValue()) {
          return value;
        }
      }

      // If not, return default
      return RESET;
    }

    private int mIntValue;

    State(int intValue) {
      mIntValue = intValue;
    }

    int getIntValue() {
      return mIntValue;
    }
  }

  final class SmoothScrollRunnable implements Runnable {
    private final Interpolator mInterpolator;
    private final int mScrollToY;
    private final int mScrollFromY;
    private final long mDuration;
    private OnSmoothScrollFinishedListener mListener;

    private boolean mContinueRunning = true;
    private long mStartTime = -1;
    private int mCurrentY = -1;

    public SmoothScrollRunnable(
        int fromY, int toY, long duration, OnSmoothScrollFinishedListener listener) {
      mScrollFromY = fromY;
      mScrollToY = toY;
      mInterpolator = mScrollAnimationInterpolator;
      mDuration = duration;
      mListener = listener;
    }

    @Override
    public void run() {

      /**
       * Only set mStartTime if this is the first time we're starting, else actually calculate the Y
       * delta
       */
      if (mStartTime == -1) {
        mStartTime = System.currentTimeMillis();
      } else {

        /**
         * We do do all calculations in long to reduce software float calculations. We use 1000 as
         * it gives us good accuracy and small rounding errors
         */
        long normalizedTime = (1000 * (System.currentTimeMillis() - mStartTime)) / mDuration;
        normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0);

        final int deltaY =
            Math.round(
                (mScrollFromY - mScrollToY)
                    * mInterpolator.getInterpolation(normalizedTime / 1000f));
        mCurrentY = mScrollFromY - deltaY;
        setHeaderScroll(mCurrentY);
      }

      // If we're not at the target Y, keep going...
      if (mContinueRunning && mScrollToY != mCurrentY) {
        ViewCompat.postOnAnimation(PullToRefreshBase.this, this);
      } else {
        if (null != mListener) {
          mListener.onSmoothScrollFinished();
        }
      }
    }

    public void stop() {
      mContinueRunning = false;
      removeCallbacks(this);
    }
  }

  static interface OnSmoothScrollFinishedListener {
    void onSmoothScrollFinished();
  }
}
  @SuppressWarnings("deprecation")
  private void init(Context context, AttributeSet attrs) {
    switch (getPullToRefreshScrollDirection()) {
      case HORIZONTAL:
        setOrientation(LinearLayout.HORIZONTAL);
        break;
      case VERTICAL:
      default:
        setOrientation(LinearLayout.VERTICAL);
        break;
    }

    setGravity(Gravity.CENTER);

    ViewConfiguration config = ViewConfiguration.get(context);
    mTouchSlop = config.getScaledTouchSlop();

    // Styleables from XML
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PullToRefresh);

    if (a.hasValue(R.styleable.PullToRefresh_ptrMode)) {
      mMode = Mode.mapIntToValue(a.getInteger(R.styleable.PullToRefresh_ptrMode, 0));
    }

    if (a.hasValue(R.styleable.PullToRefresh_ptrAnimationStyle)) {
      mLoadingAnimationStyle =
          AnimationStyle.mapIntToValue(
              a.getInteger(R.styleable.PullToRefresh_ptrAnimationStyle, 0));
    }

    // Refreshable View
    // By passing the attrs, we can add ListView/GridView params via XML
    mRefreshableView = createRefreshableView(context, attrs);
    addRefreshableView(context, mRefreshableView);

    // We need to create now layouts now
    mHeaderLayout = createLoadingLayout(context, Mode.PULL_FROM_START, a);
    mFooterLayout = createLoadingLayout(context, Mode.PULL_FROM_END, a);

    /** Styleables from XML */
    if (a.hasValue(R.styleable.PullToRefresh_ptrRefreshableViewBackground)) {
      Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrRefreshableViewBackground);
      if (null != background) {
        mRefreshableView.setBackgroundDrawable(background);
      }
    } else if (a.hasValue(R.styleable.PullToRefresh_ptrAdapterViewBackground)) {
      Utils.warnDeprecation("ptrAdapterViewBackground", "ptrRefreshableViewBackground");
      Drawable background = a.getDrawable(R.styleable.PullToRefresh_ptrAdapterViewBackground);
      if (null != background) {
        mRefreshableView.setBackgroundDrawable(background);
      }
    }

    if (a.hasValue(R.styleable.PullToRefresh_ptrOverScroll)) {
      mOverScrollEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrOverScroll, true);
    }

    if (a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
      mScrollingWhileRefreshingEnabled =
          a.getBoolean(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled, false);
    }

    // Let the derivative classes have a go at handling attributes, then
    // recycle them...
    handleStyledAttributes(a);
    a.recycle();

    // Finally update the UI for the modes
    updateUIForMode();
  }