gt:     protected void dispatchDraw(Canvas canvas) {
context: /*
   * Clover - 4chan browser https://github.com/Floens/Clover/
   * Copyright (C) 2014  Floens
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  package org.floens.chan.controller.ui;
  
  import android.content.Context;
  import android.content.res.Configuration;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.Rect;
  import android.support.v4.view.ViewCompat;
  import android.util.AttributeSet;
  import android.view.MotionEvent;
  import android.view.VelocityTracker;
  import android.view.ViewConfiguration;
  import android.widget.FrameLayout;
  import android.widget.Scroller;
  
  import org.floens.chan.controller.Controller;
  import org.floens.chan.controller.NavigationController;
  
  import static org.floens.chan.utils.AndroidUtils.dp;
  
  public class NavigationControllerContainerLayout extends FrameLayout {
      // The shadow starts at this alpha and goes up to 1f
      public static final float SHADOW_MIN_ALPHA = 0.5f;
  
  
      private NavigationController navigationController;
  
      private int slopPixels;
      private int minimalMovedPixels;
      private int flingPixels;
      private int maxFlingPixels;
  
      private boolean swipeEnabled = true;
  
      // The event used in onInterceptTouchEvent to track the initial down event
      private MotionEvent interceptedEvent;
  
      // The tracking is blocked when the user has moved too much in the y direction
      private boolean blockTracking = false;
  
      // Is the top controller being tracked and moved
      private boolean tracking = false;
  
      // The controller being tracked, corresponds with tracking
      private Controller trackingController;
  
      // The controller behind the tracking controller
      private Controller behindTrackingController;
  
      // The position of the touch after tracking has started, used to calculate the total offset from
      private int trackStartPosition;
  
      // Tracks the motion when tracking
      private VelocityTracker velocityTracker;
  
      // Used to fling and scroll the tracking view
      private Scroller scroller;
  
      // Indicate if the controller should be popped after the animation ends
      private boolean finishTransitionAfterAnimation = false;
  
      // Paint, draw rect and position for drawing the shadow
      // The shadow is only drawn when tracking is true
      private Paint shadowPaint;
      private Rect shadowRect = new Rect();
      private int shadowPosition;
  
      public NavigationControllerContainerLayout(Context context) {
          super(context);
          init();
      }
  
      public NavigationControllerContainerLayout(Context context, AttributeSet attrs) {
          super(context, attrs);
          init();
      }
  
      public NavigationControllerContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init();
      }
  
      private void init() {
          ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
          slopPixels = viewConfiguration.getScaledTouchSlop();
          minimalMovedPixels = dp(3);
          flingPixels = viewConfiguration.getScaledMinimumFlingVelocity();
          maxFlingPixels = viewConfiguration.getScaledMaximumFlingVelocity();
  
          scroller = new Scroller(getContext());
  
          shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      }
  
      public void setSwipeEnabled(boolean swipeEnabled) {
          this.swipeEnabled = swipeEnabled;
      }
  
      public void setNavigationController(NavigationController navigationController) {
          this.navigationController = navigationController;
      }
  
      @Override
      public boolean onInterceptTouchEvent(MotionEvent event) {
          if (!swipeEnabled || tracking || navigationController.isBlockingInput() || !navigationController.getTop().navigationItem.swipeable || getBelowTop() == null) {
              return false;
          }
  
          int actionMasked = event.getActionMasked();
  
          if (actionMasked != MotionEvent.ACTION_DOWN && interceptedEvent == null) {
              // Action down wasn't called here, ignore
              return false;
          }
  
          switch (actionMasked) {
              case MotionEvent.ACTION_DOWN:
  //                Logger.test("onInterceptTouchEvent down");
                  interceptedEvent = MotionEvent.obtain(event);
                  break;
              case MotionEvent.ACTION_MOVE: {
  //                Logger.test("onInterceptTouchEvent move");
                  float x = (event.getX() - interceptedEvent.getX());
                  float y = (event.getY() - interceptedEvent.getY());
  
                  if (Math.abs(y) >= slopPixels || interceptedEvent.getX() < dp(20)) {
  //                    Logger.test("blockTracking = true");
                      blockTracking = true;
                  }
  
                  if (!blockTracking && x >= minimalMovedPixels && Math.abs(x) > Math.abs(y)) {
                      startTracking(event);
  
                      return true;
                  }
                  break;
              }
              case MotionEvent.ACTION_CANCEL:
              case MotionEvent.ACTION_UP: {
  //                Logger.test("onInterceptTouchEvent cancel/up");
                  interceptedEvent.recycle();
                  interceptedEvent = null;
                  blockTracking = false;
                  break;
              }
          }
  
          return false;
      }
  
      @Override
      public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
          if (disallowIntercept) {
              if (interceptedEvent != null) {
                  interceptedEvent.recycle();
                  interceptedEvent = null;
              }
              blockTracking = false;
              if (tracking) {
                  endTracking(false);
              }
          }
  
          super.requestDisallowInterceptTouchEvent(disallowIntercept);
      }
  
      @Override
      protected void onConfigurationChanged(Configuration newConfig) {
          super.onConfigurationChanged(newConfig);
  
  //        endTracking();
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          if (!tracking) {
              return false;
          }
  
          int translationX = Math.max(0, ((int) event.getX()) - trackStartPosition);
          setTopControllerTranslation(translationX);
  
          velocityTracker.addMovement(event);
  
          switch (event.getActionMasked()) {
              case MotionEvent.ACTION_CANCEL:
              case MotionEvent.ACTION_UP: {
  //                Logger.test("onTouchEvent cancel or up");
  
                  scroller.forceFinished(true);
  
                  velocityTracker.addMovement(event);
                  velocityTracker.computeCurrentVelocity(1000);
                  int velocity = (int) velocityTracker.getXVelocity();
  
                  if (translationX > 0) {
                      boolean doFlingAway = false;
  
                      if ((velocity > 0 && Math.abs(velocity) > dp(800) && Math.abs(velocity) < maxFlingPixels) || translationX >= getWidth() * 3 / 4) {
  //                        int left = getWidth() - translationX;
  //                        int flingVelocity = Math.max(velocity, 0);
  
  //                        Logger.test("flinging with velocity = %d", velocity);
                          velocity = Math.max(dp(2000), velocity);
  
                          scroller.fling(translationX, 0, velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
  //                        Logger.test("finalX = %d getWidth = %d", scroller.getFinalX(), getWidth());
  
                          // Make sure the animation always goes past the end
                          if (scroller.getFinalX() < getWidth()) {
                              scroller.startScroll(translationX, 0, getWidth(), 0, 2000);
                          }
  
                          doFlingAway = true;
  //                        Logger.test("Flinging away with velocity = %d", velocity);
                      }
  
                      if (doFlingAway) {
                          startFlingAnimation(true);
                      } else {
  //                        Logger.test("Snapping back");
                          scroller.forceFinished(true);
                          scroller.startScroll(translationX, 0, -translationX, 0, 250);
                          startFlingAnimation(false);
                      }
                  } else {
                      // User swiped back to the left
                      endTracking(false);
                  }
  
                  velocityTracker.recycle();
                  velocityTracker = null;
  
                  break;
              }
          }
  
          return true;
      }
  
      @Override
  