whole_func_string: function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
    var userAgent = navigator.userAgent || navigator.vendor || window.opera;
    var isIos = userAgent.match(/ipad|iphone|ipod/i);
    var isAndroid = userAgent.match(/android/i);
    var touchActionProperty = getTouchAction();
    var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
  
    var self = {
      handler: addHandler,
      register: register,
      isAndroid: isAndroid,
      isIos: isIos,
      // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?
      isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack
    };
  
    if (self.isHijackingClicks) {
      self.handler('click', {
        options: {
          maxDistance: maxClickDistance
        },
        onEnd: checkDistanceAndEmit('click')
      });
  
      self.handler('focus', {
        options: {
          maxDistance: maxClickDistance
        },
        onEnd: function(ev, pointer) {
          if (pointer.distance < this.state.options.maxDistance && canFocus(ev.target)) {
            this.dispatchEvent(ev, 'focus', pointer);
            ev.target.focus();
          }
        }
      });
  
      self.handler('mouseup', {
        options: {
          maxDistance: maxClickDistance
        },
        onEnd: checkDistanceAndEmit('mouseup')
      });
  
      self.handler('mousedown', {
        onStart: function(ev) {
          this.dispatchEvent(ev, 'mousedown');
        }
      });
    }
  
    function checkDistanceAndEmit(eventName) {
      return function(ev, pointer) {
        if (pointer.distance < this.state.options.maxDistance) {
          this.dispatchEvent(ev, eventName, pointer);
        }
      };
    }
  
    /**
     * Register an element to listen for a handler.
     * This allows an element to override the default options for a handler.
     * Additionally, some handlers like drag and hold only dispatch events if
     * the domEvent happens inside an element that's registered to listen for these events.
     *
     * @see GestureHandler for how overriding of default options works.
     * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horizontal: false })
     */
    function register(element, handlerName, options) {
      var handler = HANDLERS[handlerName.replace(/^\$md./, '')];
      if (!handler) {
        throw new Error('Failed to register element with handler ' + handlerName + '. ' +
        'Available handlers: ' + Object.keys(HANDLERS).join(', '));
      }
      return handler.registerElement(element, options);
    }
  
    /*
     * add a handler to $mdGesture. see below.
     */
    function addHandler(name, definition) {
      var handler = new $$MdGestureHandler(name);
      angular.extend(handler, definition);
      HANDLERS[name] = handler;
  
      return self;
    }
  
    /**
     * Register handlers. These listen to touch/start/move events, interpret them,
     * and dispatch gesture events depending on options & conditions. These are all
     * instances of GestureHandler.
     * @see GestureHandler
     */
    return self
      /*
       * The press handler dispatches an event on touchdown/touchend.
       * It's a simple abstraction of touch/mouse/pointer start and end.
       */
      .handler('press', {
        onStart: function (ev, pointer) {
          this.dispatchEvent(ev, '$md.pressdown');
        },
        onEnd: function (ev, pointer) {
          this.dispatchEvent(ev, '$md.pressup');
        }
      })
  
      /*
       * The hold handler dispatches an event if the user keeps their finger within
       * the same <maxDistance> area for <delay> ms.
       * The hold handler will only run if a parent of the touch target is registered
       * to listen for hold events through $mdGesture.register()
       */
      .handler('hold', {
        options: {
          maxDistance: 6,
          delay: 500
        },
        onCancel: function () {
          $timeout.cancel(this.state.timeout);
        },
        onStart: function (ev, pointer) {
          // For hold, require a parent to be registered with $mdGesture.register()
          // Because we prevent scroll events, this is necessary.
          if (!this.state.registeredParent) return this.cancel();
  
          this.state.pos = {x: pointer.x, y: pointer.y};
          this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
            this.dispatchEvent(ev, '$md.hold');
            this.cancel(); //we're done!
          }), this.state.options.delay, false);
        },
        onMove: function (ev, pointer) {
          // Don't scroll while waiting for hold.
          // If we don't preventDefault touchmove events here, Android will assume we don't
          // want to listen to anymore touch events. It will start scrolling and stop sending
          // touchmove events.
          if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
  
          // If the user moves greater than <maxDistance> pixels, stop the hold timer
          // set in onStart
          var dx = this.state.pos.x - pointer.x;
          var dy = this.state.pos.y - pointer.y;
          if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {
            this.cancel();
          }
        },
        onEnd: function () {
          this.onCancel();
        }
      })
  
      /*
       * The drag handler dispatches a drag event if the user holds and moves his finger greater than
       * <minDistance> px in the x or y direction, depending on options.horizontal.
       * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
       * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
       * pixels vertically, this handler won't consider the move part of a drag.
       */
      .handler('drag', {
        options: {
          minDistance: 6,
          horizontal: true,
          cancelMultiplier: 1.5
        },
        onSetup: function(element, options) {
          if (touchActionProperty) {
            // We check for horizontal to be false, because otherwise we would overwrite the default opts.
            this.oldTouchAction = element[0].style[touchActionProperty];
            element[0].style[touchActionProperty] = options.horizontal ? 'pan-y' : 'pan-x';
          }
        },
        onCleanup: function(element) {
          if (this.oldTouchAction) {
            element[0].style[touchActionProperty] = this.oldTouchAction;
          }
        },
        onStart: function (ev) {
          // For drag, require a parent to be registered with $mdGesture.register()
          if (!this.state.registeredParent) this.cancel();
        },
        onMove: function (ev, pointer) {
          var shouldStartDrag, shouldCancel;
          // Don't scroll while deciding if this touchmove qualifies as a drag event.
          // If we don't preventDefault touchmove events here, Android will assume we don't
          // want to listen to anymore touch events. It will start scrolling and stop sending
          // touchmove events.
          if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
  
          if (!this.state.dragPointer) {
            if (this.state.options.horizontal) {
              shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
              shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;
            } else {
              shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
              shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;
            }
  
            if (shouldStartDrag) {
              // Create a new pointer representing this drag, starting at this point where the drag started.
              this.state.dragPointer = makeStartPointer(ev);
              updatePointerState(ev, this.state.dragPointer);
              this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
  
            } else if (shouldCancel) {
              this.cancel();
            }
          } else {
            this.dispatchDragMove(ev);
          }
        },
        // Only dispatch dragmove events every frame; any more is unnecessary
        dispatchDragMove: $$rAF.throttle(function (ev) {
          // Make sure the drag didn't stop while waiting for the next frame
          if (this.state.isRunning) {
            updatePointerState(ev, this.state.dragPointer);
            this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
          }
        }),
        onEnd: function (ev, pointer) {
          if (this.state.dragPointer) {
            updatePointerState(ev, this.state.dragPointer);
            this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
          }
        }
      })
  
      /*
       * The swipe handler will dispatch a swipe event if, on the end of a touch,
       * the velocity and distance were high enough.
       */
      .handler('swipe', {
        options: {
          minVelocity: 0.65,
          minDistance: 10
        },
        onEnd: function (ev, pointer) {
          var eventType;
  
          if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
            Math.abs(pointer.distanceX) > this.state.options.minDistance) {
            eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
            this.dispatchEvent(ev, eventType);
          }
          else if (Math.abs(pointer.velocityY) > this.state.options.minVelocity &&
            Math.abs(pointer.distanceY) > this.state.options.minDistance) {
            eventType = pointer.directionY == 'up' ? '$md.swipeup' : '$md.swipedown';
            this.dispatchEvent(ev, eventType);
          }
        }
      });
  
    function getTouchAction() {
      var testEl = document.createElement('div');
      var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
  
      for (var i = 0; i < vendorPrefixes.length; i++) {
        var prefix = vendorPrefixes[i];
        var property = prefix ? prefix + 'TouchAction' : 'touchAction';
        if (angular.isDefined(testEl.style[property])) {
          return property;
        }
      }
    }
  
  }
func_code_string: function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
    var userAgent = navigator.userAgent || navigator.vendor || window.opera;
    var isIos = userAgent.match(/ipad|iphone|ipod/i);
    var isAndroid = userAgent.match(/android/i);
    var touchActionProperty = getTouchAction();
    var hasJQuery =  (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
  
    var self = {
      handler: addHandler,
      register: register,
      isAndroid: isAndroid,
      isIos: isIos,
      // On mobile w/out jQuery, we normally intercept clicks. Should we skip that?
      isHijackingClicks: (isIos || isAndroid) && !hasJQuery && !forceSkipClickHijack
    };
  
    if (self.isHijackingClicks) {
      self.handler('click', {
        options: {
          maxDistance: maxClickDistance
        },
        onEnd: checkDistanceAndEmit('click')
      });
  
      self.handler('focus', {
        options: {
          maxDistance: maxClickDistance
        },
        onEnd: function(ev, pointer) {
          if (pointer.distance < this.state.options.maxDistance && canFocus(ev.target)) {
            this.dispatchEvent(ev, 'focus', pointer);
            ev.target.focus();
          }
        }
      });
  
      self.handler('mouseup', {
        options: {
          maxDistance: maxClickDistance
        },
        onEnd: checkDistanceAndEmit('mouseup')
      });
  
      self.handler('mousedown', {
        onStart: function(ev) {
          this.dispatchEvent(ev, 'mousedown');
        }
      });
    }
  
    function checkDistanceAndEmit(eventName) {
      return function(ev, pointer) {
        if (pointer.distance < this.state.options.maxDistance) {
          this.dispatchEvent(ev, eventName, pointer);
        }
      };
    }
  
    /**
     * Register an element to listen for a handler.
     * This allows an element to override the default options for a handler.
     * Additionally, some handlers like drag and hold only dispatch events if
     * the domEvent happens inside an element that's registered to listen for these events.
     *
     * @see GestureHandler for how overriding of default options works.
     * @example $mdGesture.register(myElement, 'drag', { minDistance: 20, horizontal: false })
     */
    function register(element, handlerName, options) {
      var handler = HANDLERS[handlerName.replace(/^\$md./, '')];
      if (!handler) {
        throw new Error('Failed to register element with handler ' + handlerName + '. ' +
        'Available handlers: ' + Object.keys(HANDLERS).join(', '));
      }
      return handler.registerElement(element, options);
    }
  
    /*
     * add a handler to $mdGesture. see below.
     */
    function addHandler(name, definition) {
      var handler = new $$MdGestureHandler(name);
      angular.extend(handler, definition);
      HANDLERS[name] = handler;
  
      return self;
    }
  
    /**
     * Register handlers. These listen to touch/start/move events, interpret them,
     * and dispatch gesture events depending on options & conditions. These are all
     * instances of GestureHandler.
     * @see GestureHandler
     */
    return self
      /*
       * The press handler dispatches an event on touchdown/touchend.
       * It's a simple abstraction of touch/mouse/pointer start and end.
       */
      .handler('press', {
        onStart: function (ev, pointer) {
          this.dispatchEvent(ev, '$md.pressdown');
        },
        onEnd: function (ev, pointer) {
          this.dispatchEvent(ev, '$md.pressup');
        }
      })
  
      /*
       * The hold handler dispatches an event if the user keeps their finger within
       * the same <maxDistance> area for <delay> ms.
       * The hold handler will only run if a parent of the touch target is registered
       * to listen for hold events through $mdGesture.register()
       */
      .handler('hold', {
        options: {
          maxDistance: 6,
          delay: 500
        },
        onCancel: function () {
          $timeout.cancel(this.state.timeout);
        },
        onStart: function (ev, pointer) {
          // For hold, require a parent to be registered with $mdGesture.register()
          // Because we prevent scroll events, this is necessary.
          if (!this.state.registeredParent) return this.cancel();
  
          this.state.pos = {x: pointer.x, y: pointer.y};
          this.state.timeout = $timeout(angular.bind(this, function holdDelayFn() {
            this.dispatchEvent(ev, '$md.hold');
            this.cancel(); //we're done!
          }), this.state.options.delay, false);
        },
        onMove: function (ev, pointer) {
          // Don't scroll while waiting for hold.
          // If we don't preventDefault touchmove events here, Android will assume we don't
          // want to listen to anymore touch events. It will start scrolling and stop sending
          // touchmove events.
          if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
  
          // If the user moves greater than <maxDistance> pixels, stop the hold timer
          // set in onStart
          var dx = this.state.pos.x - pointer.x;
          var dy = this.state.pos.y - pointer.y;
          if (Math.sqrt(dx * dx + dy * dy) > this.options.maxDistance) {
            this.cancel();
          }
        },
        onEnd: function () {
          this.onCancel();
        }
      })
  
      /*
       * The drag handler dispatches a drag event if the user holds and moves his finger greater than
       * <minDistance> px in the x or y direction, depending on options.horizontal.
       * The drag will be cancelled if the user moves his finger greater than <minDistance>*<cancelMultiplier> in
       * the perpendicular direction. Eg if the drag is horizontal and the user moves his finger <minDistance>*<cancelMultiplier>
       * pixels vertically, this handler won't consider the move part of a drag.
       */
      .handler('drag', {
        options: {
          minDistance: 6,
          horizontal: true,
          cancelMultiplier: 1.5
        },
        onSetup: function(element, options) {
          if (touchActionProperty) {
            // We check for horizontal to be false, because otherwise we would overwrite the default opts.
            this.oldTouchAction = element[0].style[touchActionProperty];
            element[0].style[touchActionProperty] = options.horizontal ? 'pan-y' : 'pan-x';
          }
        },
        onCleanup: function(element) {
          if (this.oldTouchAction) {
            element[0].style[touchActionProperty] = this.oldTouchAction;
          }
        },
        onStart: function (ev) {
          // For drag, require a parent to be registered with $mdGesture.register()
          if (!this.state.registeredParent) this.cancel();
        },
        onMove: function (ev, pointer) {
          var shouldStartDrag, shouldCancel;
          // Don't scroll while deciding if this touchmove qualifies as a drag event.
          // If we don't preventDefault touchmove events here, Android will assume we don't
          // want to listen to anymore touch events. It will start scrolling and stop sending
          // touchmove events.
          if (!touchActionProperty && ev.type === 'touchmove') ev.preventDefault();
  
          if (!this.state.dragPointer) {
            if (this.state.options.horizontal) {
              shouldStartDrag = Math.abs(pointer.distanceX) > this.state.options.minDistance;
              shouldCancel = Math.abs(pointer.distanceY) > this.state.options.minDistance * this.state.options.cancelMultiplier;
            } else {
              shouldStartDrag = Math.abs(pointer.distanceY) > this.state.options.minDistance;
              shouldCancel = Math.abs(pointer.distanceX) > this.state.options.minDistance * this.state.options.cancelMultiplier;
            }
  
            if (shouldStartDrag) {
              // Create a new pointer representing this drag, starting at this point where the drag started.
              this.state.dragPointer = makeStartPointer(ev);
              updatePointerState(ev, this.state.dragPointer);
              this.dispatchEvent(ev, '$md.dragstart', this.state.dragPointer);
  
            } else if (shouldCancel) {
              this.cancel();
            }
          } else {
            this.dispatchDragMove(ev);
          }
        },
        // Only dispatch dragmove events every frame; any more is unnecessary
        dispatchDragMove: $$rAF.throttle(function (ev) {
          // Make sure the drag didn't stop while waiting for the next frame
          if (this.state.isRunning) {
            updatePointerState(ev, this.state.dragPointer);
            this.dispatchEvent(ev, '$md.drag', this.state.dragPointer);
          }
        }),
        onEnd: function (ev, pointer) {
          if (this.state.dragPointer) {
            updatePointerState(ev, this.state.dragPointer);
            this.dispatchEvent(ev, '$md.dragend', this.state.dragPointer);
          }
        }
      })
  
      /*
       * The swipe handler will dispatch a swipe event if, on the end of a touch,
       * the velocity and distance were high enough.
       */
      .handler('swipe', {
        options: {
          minVelocity: 0.65,
          minDistance: 10
        },
        onEnd: function (ev, pointer) {
          var eventType;
  
          if (Math.abs(pointer.velocityX) > this.state.options.minVelocity &&
            Math.abs(pointer.distanceX) > this.state.options.minDistance) {
            eventType = pointer.directionX == 'left' ? '$md.swipeleft' : '$md.swiperight';
            this.dispatchEvent(ev, eventType);
          }
          else if (Math.abs(pointer.velocityY) > this.state.options.minVelocity &&
            Math.abs(pointer.distanceY) > this.state.options.minDistance) {
            eventType = pointer.directionY == 'up' ? '$md.swipeup' : '$md.swipedown';
            this.dispatchEvent(ev, eventType);
          }
        }
      });
  
    function getTouchAction() {
      var testEl = document.createElement('div');
      var vendorPrefixes = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
  
      for (var i = 0; i < vendorPrefixes.length; i++) {
        var prefix = vendorPrefixes[i];
        var property = prefix ? prefix + 'TouchAction' : 'touchAction';
        if (angular.isDefined(testEl.style[property])) {
          return property;
        }
      }
    }
  
  }
func_documentation_string: MdGesture factory construction function
  @ngInject