function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming, $timeout) {
  RadioGroupController.prototype = createRadioGroupControllerProto();

  return {
    restrict: 'E',
    controller: ['$element', RadioGroupController],
    require: ['mdRadioGroup', '?ngModel'],
    link: { pre: linkRadioGroup }
  };

  function linkRadioGroup(scope, element, attr, controllers) {
    // private md component indicator for styling
    element.addClass('_md');
    $mdTheming(element);

    var radioGroupController = controllers[0];
    var ngModelCtrl = controllers[1] || $mdUtil.fakeNgModel();

    radioGroupController.init(ngModelCtrl);

    scope.mouseActive = false;

    element
      .attr({
        'role': 'radiogroup',
        'tabIndex': element.attr('tabindex') || '0'
      })
      .on('keydown', keydownListener)
      .on('mousedown', function() {
        scope.mouseActive = true;
        $timeout(function() {
          scope.mouseActive = false;
        }, 100);
      })
      .on('focus', function() {
        if (scope.mouseActive === false) {
          radioGroupController.$element.addClass('md-focused');
        }
      })
      .on('blur', function() {
        radioGroupController.$element.removeClass('md-focused');
      });

    // Initially set the first radio button as the aria-activedescendant. This will be overridden
    // if a 'checked' radio button gets rendered. We need to wait for the nextTick here so that the
    // radio buttons have their id values assigned.
    $mdUtil.nextTick(function () {
      var radioButtons = getRadioButtons(radioGroupController.$element);
      if (radioButtons.count() &&
          !radioGroupController.$element[0].hasAttribute('aria-activedescendant')) {
        radioGroupController.setActiveDescendant(radioButtons.first().id);
      }
    });

    /**
     * Apply the md-focused class if it isn't already applied.
     */
    function setFocus() {
      if (!element.hasClass('md-focused')) { element.addClass('md-focused'); }
    }

    /**
     * @param {KeyboardEvent} keyboardEvent
     */
    function keydownListener(keyboardEvent) {
      var keyCode = keyboardEvent.which || keyboardEvent.keyCode;

      // Only listen to events that we originated ourselves
      // so that we don't trigger on things like arrow keys in inputs.
      if (keyCode !== $mdConstant.KEY_CODE.ENTER &&
          keyboardEvent.currentTarget !== keyboardEvent.target) {
        return;
      }

      switch (keyCode) {
        case $mdConstant.KEY_CODE.LEFT_ARROW:
        case $mdConstant.KEY_CODE.UP_ARROW:
          keyboardEvent.preventDefault();
          radioGroupController.selectPrevious();
          setFocus();
          break;

        case $mdConstant.KEY_CODE.RIGHT_ARROW:
        case $mdConstant.KEY_CODE.DOWN_ARROW:
          keyboardEvent.preventDefault();
          radioGroupController.selectNext();
          setFocus();
          break;

        case $mdConstant.KEY_CODE.SPACE:
          keyboardEvent.preventDefault();
          radioGroupController.selectCurrent();
          break;

        case $mdConstant.KEY_CODE.ENTER:
          var form = angular.element($mdUtil.getClosest(element[0], 'form'));
          if (form.length > 0) {
            form.triggerHandler('submit');
          }
          break;
      }
    }
  }

  /**
   * @param {JQLite} $element
   * @constructor
   */
  function RadioGroupController($element) {
    this._radioButtonRenderFns = [];
    this.$element = $element;
  }

  function createRadioGroupControllerProto() {
    return {
      init: function(ngModelCtrl) {
        this._ngModelCtrl = ngModelCtrl;
        this._ngModelCtrl.$render = angular.bind(this, this.render);
      },
      add: function(rbRender) {
        this._radioButtonRenderFns.push(rbRender);
      },
      remove: function(rbRender) {
        var index = this._radioButtonRenderFns.indexOf(rbRender);
        if (index !== -1) {
          this._radioButtonRenderFns.splice(index, 1);
        }
      },
      render: function() {
        this._radioButtonRenderFns.forEach(function(rbRender) {
          rbRender();
        });
      },
      setViewValue: function(value, eventType) {
        this._ngModelCtrl.$setViewValue(value, eventType);
        // update the other radio buttons as well
        this.render();
      },
      getViewValue: function() {
        return this._ngModelCtrl.$viewValue;
      },
      selectCurrent: function() {
        return changeSelectedButton(this.$element, incrementSelection.CURRENT);
      },
      selectNext: function() {
        return changeSelectedButton(this.$element, incrementSelection.NEXT);
      },
      selectPrevious: function() {
        return changeSelectedButton(this.$element, incrementSelection.PREVIOUS);
      },
      setActiveDescendant: function (radioId) {
        this.$element.attr('aria-activedescendant', radioId);
      },
      isDisabled: function() {
        return this.$element[0].hasAttribute('disabled');
      }
    };
  }

  /**
   * Coerce all child radio buttons into an array, then wrap them in an iterator.
   * @param parent {!JQLite}
   * @return {{add: add, next: (function()), last: (function(): any|null), previous: (function()), count: (function(): number), hasNext: (function(*=): Array.length|*|number|boolean), inRange: (function(*): boolean), remove: remove, contains: (function(*=): *|boolean), itemAt: (function(*=): any|null), findBy: (function(*, *): *[]), hasPrevious: (function(*=): Array.length|*|number|boolean), items: (function(): *[]), indexOf: (function(*=): number), first: (function(): any|null)}}
   */
  function getRadioButtons(parent) {
    return $mdUtil.iterator(parent[0].querySelectorAll('md-radio-button'), true);
  }

  /**
   * Change the radio group's selected button by a given increment.
   * If no button is selected, select the first button.
   * @param {JQLite} parent the md-radio-group
   * @param {incrementSelection} increment enum that determines whether the next or
   *  previous button is clicked. For current, only the first button is selected, otherwise the
   *  current selection is maintained (by doing nothing).
   */
  function changeSelectedButton(parent, increment) {
    var buttons = getRadioButtons(parent);
    var target;

    if (buttons.count()) {
      var validate = function (button) {
        // If disabled, then NOT valid
        return !angular.element(button).attr("disabled");
      };

      var selected = parent[0].querySelector('md-radio-button.md-checked');
      if (!selected) {
        target = buttons.first();
      } else if (increment === incrementSelection.PREVIOUS ||
                 increment === incrementSelection.NEXT) {
        target = buttons[
          increment === incrementSelection.PREVIOUS ? 'previous' : 'next'
        ](selected, validate);
      }

      if (target) {
        // Activate radioButton's click listener (triggerHandler won't create a real click event)
        angular.element(target).triggerHandler('click');
      }
    }
  }
}
@ngdoc directive
@module material.components.radioButton
@name mdRadioGroup

@restrict E

@description
The `<md-radio-group>` directive identifies a grouping
container for the 1..n grouped radio buttons; specified using nested
`<md-radio-button>` elements.

The radio button uses the accent color by default. The primary color palette may be used with
the `md-primary` class.

Note: `<md-radio-group>` and `<md-radio-button>` handle `tabindex` differently
than the native `<input type="radio">` controls. Whereas the native controls
force the user to tab through all the radio buttons, `<md-radio-group>`
is focusable and by default the `<md-radio-button>`s are not.

@param {string} ng-model Assignable angular expression to data-bind to.
@param {string=} ng-change AngularJS expression to be executed when input changes due to user
   interaction.
@param {boolean=} md-no-ink If present, disables ink ripple effects.

@usage
<hljs lang="html">
<md-radio-group ng-model="selected">
  <md-radio-button ng-repeat="item in items"
                   ng-value="item.value" aria-label="{{item.label}}">
    {{ item.label }}
  </md-radio-button>
</md-radio-group>
</hljs>