List<List<MonthCellDescriptor>> getMonthCells(MonthDescriptor month, Calendar startCal) {
    Calendar cal = Calendar.getInstance(locale);
    cal.setTime(startCal.getTime());
    List<List<MonthCellDescriptor>> cells = new ArrayList<List<MonthCellDescriptor>>();
    cal.set(DAY_OF_MONTH, 1);
    int firstDayOfWeek = cal.get(DAY_OF_WEEK);
    int offset = cal.getFirstDayOfWeek() - firstDayOfWeek;
    if (offset > 0) {
      offset -= 7;
    }
    cal.add(Calendar.DATE, offset);

    Calendar minSelectedCal = minDate(selectedCals);
    Calendar maxSelectedCal = maxDate(selectedCals);

    while ((cal.get(MONTH) < month.getMonth() + 1 || cal.get(YEAR) < month.getYear()) //
        && cal.get(YEAR) <= month.getYear()) {
      Logr.d("Building week row starting at %s", cal.getTime());
      List<MonthCellDescriptor> weekCells = new ArrayList<MonthCellDescriptor>();
      cells.add(weekCells);
      for (int c = 0; c < 7; c++) {
        Date date = cal.getTime();
        boolean isCurrentMonth = cal.get(MONTH) == month.getMonth();
        boolean isSelected = isCurrentMonth && containsDate(selectedCals, cal);
        boolean isSelectable =
            isCurrentMonth && betweenDates(cal, minCal, maxCal) && isDateSelectable(date);
        boolean isToday = sameDate(cal, today);
        boolean isHighlighted = containsDate(highlightedCals, cal);
        int value = cal.get(DAY_OF_MONTH);

        MonthCellDescriptor.RangeState rangeState = MonthCellDescriptor.RangeState.NONE;
        if (selectedCals.size() > 1) {
          if (sameDate(minSelectedCal, cal)) {
            rangeState = MonthCellDescriptor.RangeState.FIRST;
          } else if (sameDate(maxDate(selectedCals), cal)) {
            rangeState = MonthCellDescriptor.RangeState.LAST;
          } else if (betweenDates(cal, minSelectedCal, maxSelectedCal)) {
            rangeState = MonthCellDescriptor.RangeState.MIDDLE;
          }
        }

        weekCells.add(
            new MonthCellDescriptor(
                date,
                isCurrentMonth,
                isSelectable,
                isSelected,
                isToday,
                isHighlighted,
                value,
                rangeState));
        cal.add(DATE, 1);
      }
    }
    return cells;
  }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   Logr.d(
       "Grid.onMeasure w=%s h=%s",
       MeasureSpec.toString(widthMeasureSpec), MeasureSpec.toString(heightMeasureSpec));
   int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
   if (oldWidthMeasureSize == widthMeasureSize) {
     Logr.d("SKIP Grid.onMeasure");
     setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
     return;
   }
   long start = System.currentTimeMillis();
   oldWidthMeasureSize = widthMeasureSize;
   int cellSize = widthMeasureSize / 7;
   // Remove any extra pixels since /7 is unlikely to give whole nums.
   widthMeasureSize = cellSize * 7;
   int totalHeight = 0;
   final int rowWidthSpec = makeMeasureSpec(widthMeasureSize, EXACTLY);
   final int rowHeightSpec = makeMeasureSpec(cellSize, EXACTLY);
   for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) {
     final View child = getChildAt(c);
     if (child.getVisibility() == View.VISIBLE) {
       if (c == 0) { // It's the header: height should be wrap_content.
         measureChild(child, rowWidthSpec, makeMeasureSpec(cellSize, AT_MOST));
       } else {
         measureChild(child, rowWidthSpec, rowHeightSpec);
       }
       totalHeight += child.getMeasuredHeight();
     }
   }
   final int measuredWidth = widthMeasureSize + 2; // Fudge factor to make the borders show up.
   setMeasuredDimension(measuredWidth, totalHeight);
   Logr.d("Grid.onMeasure %d ms", System.currentTimeMillis() - start);
 }
 /**
  * This method should only be called if the calendar is contained in a dialog, and it should only
  * be called when the screen has been rotated and the dialog should be re-measured.
  */
 public void unfixDialogDimens() {
   Logr.d("Reset the fixed dimensions to allow for re-measurement");
   // Fix the layout height/width after the dialog has been shown.
   getLayoutParams().height = LayoutParams.MATCH_PARENT;
   getLayoutParams().width = LayoutParams.MATCH_PARENT;
   requestLayout();
 }
 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   long start = System.currentTimeMillis();
   int cellHeight = bottom - top;
   for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) {
     final View child = getChildAt(c);
     child.layout(c * cellSize, 0, (c + 1) * cellSize, cellHeight);
   }
   Logr.d("Row.onLayout %d ms", System.currentTimeMillis() - start);
 }
 @Override
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
   long start = System.currentTimeMillis();
   top = 0;
   for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) {
     final View child = getChildAt(c);
     final int rowHeight = child.getMeasuredHeight();
     child.layout(left, top, right, top + rowHeight);
     top += rowHeight;
   }
   Logr.d("Grid.onLayout %d ms", System.currentTimeMillis() - start);
 }
 /**
  * This method should only be called if the calendar is contained in a dialog, and it should only
  * be called once, right after the dialog is shown (using {@link
  * android.content.DialogInterface.OnShowListener} or {@link
  * android.app.DialogFragment#onStart()}).
  */
 public void fixDialogDimens() {
   Logr.d("Fixing dimensions to h = %d / w = %d", getMeasuredHeight(), getMeasuredWidth());
   // Fix the layout height/width after the dialog has been shown.
   getLayoutParams().height = getMeasuredHeight();
   getLayoutParams().width = getMeasuredWidth();
   // Post this runnable so it runs _after_ the dimen changes have been
   // applied/re-measured.
   post(
       new Runnable() {
         @Override
         public void run() {
           Logr.d("Dimens are fixed: now scroll to the selected date");
           scrollToSelectedDates();
         }
       });
 }
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   long start = System.currentTimeMillis();
   final int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
   cellSize = totalWidth / 7;
   int cellWidthSpec = makeMeasureSpec(cellSize, EXACTLY);
   int cellHeightSpec = isHeaderRow ? makeMeasureSpec(cellSize, AT_MOST) : cellWidthSpec;
   int rowHeight = 0;
   for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) {
     final View child = getChildAt(c);
     child.measure(cellWidthSpec, cellHeightSpec);
     // The row height is the height of the tallest cell.
     if (child.getMeasuredHeight() > rowHeight) {
       rowHeight = child.getMeasuredHeight();
     }
   }
   final int widthWithPadding = totalWidth + getPaddingLeft() + getPaddingRight();
   final int heightWithPadding = rowHeight + getPaddingTop() + getPaddingBottom();
   setMeasuredDimension(widthWithPadding, heightWithPadding);
   Logr.d("Row.onMeasure %d ms", System.currentTimeMillis() - start);
 }