SimpleMonthView.java revision c5b95c20b6fd3f4e63147efb22dd19c657b17001
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.Configuration;
22import android.content.res.Resources;
23import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.Paint.Align;
26import android.graphics.Paint.Style;
27import android.graphics.Rect;
28import android.graphics.Typeface;
29import android.os.Bundle;
30import android.text.format.DateFormat;
31import android.text.format.DateUtils;
32import android.text.format.Time;
33import android.util.AttributeSet;
34import android.util.IntArray;
35import android.util.StateSet;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.accessibility.AccessibilityEvent;
39import android.view.accessibility.AccessibilityNodeInfo;
40
41import com.android.internal.R;
42import com.android.internal.widget.ExploreByTouchHelper;
43
44import java.text.SimpleDateFormat;
45import java.util.Calendar;
46import java.util.Formatter;
47import java.util.Locale;
48
49/**
50 * A calendar-like view displaying a specified month and the appropriate selectable day numbers
51 * within the specified month.
52 */
53class SimpleMonthView extends View {
54    private static final int MIN_ROW_HEIGHT = 10;
55
56    private static final int DEFAULT_SELECTED_DAY = -1;
57    private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
58    private static final int DEFAULT_NUM_DAYS = 7;
59    private static final int DEFAULT_NUM_ROWS = 6;
60    private static final int MAX_NUM_ROWS = 6;
61
62    private static final int DAY_SEPARATOR_WIDTH = 1;
63
64    private final Formatter mFormatter;
65    private final StringBuilder mStringBuilder;
66
67    private final int mMonthTextSize;
68    private final int mDayOfWeekTextSize;
69    private final int mDayTextSize;
70
71    private final int mMonthHeaderHeight;
72
73    private final Paint mMonthPaint = new Paint();
74    private final Paint mDayOfWeekPaint = new Paint();
75    private final Paint mDayPaint = new Paint();
76    private final Paint mDayBackgroundPaint = new Paint();
77
78    /** Single-letter (when available) formatter for the day of week label. */
79    private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault());
80
81    // affects the padding on the sides of this view
82    private int mPadding = 0;
83
84    private String mDayOfWeekTypeface;
85    private String mMonthTypeface;
86
87    private int mMonth;
88    private int mYear;
89
90    // Quick reference to the width of this view, matches parent
91    private int mWidth;
92
93    // The height this view should draw at in pixels, set by height param
94    private final int mRowHeight;
95
96    // If this view contains the today
97    private boolean mHasToday = false;
98
99    // Which day is selected [0-6] or -1 if no day is selected
100    private int mActivatedDay = -1;
101
102    // Which day is today [0-6] or -1 if no day is today
103    private int mToday = DEFAULT_SELECTED_DAY;
104
105    // Which day of the week to start on [0-6]
106    private int mWeekStart = DEFAULT_WEEK_START;
107
108    // How many days to display
109    private int mNumDays = DEFAULT_NUM_DAYS;
110
111    // The number of days + a spot for week number if it is displayed
112    private int mNumCells = mNumDays;
113
114    private int mDayOfWeekStart = 0;
115
116    // First enabled day
117    private int mEnabledDayStart = 1;
118
119    // Last enabled day
120    private int mEnabledDayEnd = 31;
121
122    private final Calendar mCalendar = Calendar.getInstance();
123    private final Calendar mDayLabelCalendar = Calendar.getInstance();
124
125    private final MonthViewTouchHelper mTouchHelper;
126
127    private int mNumRows = DEFAULT_NUM_ROWS;
128
129    // Optional listener for handling day click actions
130    private OnDayClickListener mOnDayClickListener;
131
132    // Whether to prevent setting the accessibility delegate
133    private boolean mLockAccessibilityDelegate;
134
135    private int mNormalTextColor;
136    private int mDisabledTextColor;
137    private int mSelectedDayColor;
138
139    private ColorStateList mDayTextColor;
140
141    public SimpleMonthView(Context context) {
142        this(context, null);
143    }
144
145    public SimpleMonthView(Context context, AttributeSet attrs) {
146        this(context, attrs, R.attr.datePickerStyle);
147    }
148
149    public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) {
150        this(context, attrs, defStyleAttr, 0);
151    }
152
153    public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
154        super(context, attrs, defStyleAttr, defStyleRes);
155
156        final Resources res = context.getResources();
157        mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
158        mMonthTypeface = res.getString(R.string.sans_serif);
159
160        mStringBuilder = new StringBuilder(50);
161        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
162
163        mDayTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size);
164        mMonthTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size);
165        mDayOfWeekTextSize = res.getDimensionPixelSize(
166                R.dimen.datepicker_month_day_label_text_size);
167        mMonthHeaderHeight = res.getDimensionPixelOffset(
168                R.dimen.datepicker_month_list_item_header_height);
169
170        mRowHeight = Math.max(MIN_ROW_HEIGHT,
171                (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height)
172                        - mMonthHeaderHeight) / MAX_NUM_ROWS);
173
174        // Set up accessibility components.
175        mTouchHelper = new MonthViewTouchHelper(this);
176        setAccessibilityDelegate(mTouchHelper);
177        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
178        mLockAccessibilityDelegate = true;
179
180        initPaints();
181    }
182
183    /**
184     * Sets up the text and style properties for painting.
185     */
186    private void initPaints() {
187        mMonthPaint.setAntiAlias(true);
188        mMonthPaint.setTextSize(mMonthTextSize);
189        mMonthPaint.setTypeface(Typeface.create(mMonthTypeface, Typeface.BOLD));
190        mMonthPaint.setTextAlign(Align.CENTER);
191        mMonthPaint.setStyle(Style.FILL);
192
193        mDayOfWeekPaint.setAntiAlias(true);
194        mDayOfWeekPaint.setTextSize(mDayOfWeekTextSize);
195        mDayOfWeekPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.BOLD));
196        mDayOfWeekPaint.setTextAlign(Align.CENTER);
197        mDayOfWeekPaint.setStyle(Style.FILL);
198
199        mDayBackgroundPaint.setAntiAlias(true);
200        mDayBackgroundPaint.setStyle(Style.FILL);
201
202        mDayPaint.setAntiAlias(true);
203        mDayPaint.setTextSize(mDayTextSize);
204        mDayPaint.setTextAlign(Align.CENTER);
205        mDayPaint.setStyle(Style.FILL);
206    }
207
208    @Override
209    protected void onConfigurationChanged(Configuration newConfig) {
210        super.onConfigurationChanged(newConfig);
211
212        mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale);
213    }
214
215    void setMonthTextColor(ColorStateList monthTextColor) {
216        final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0);
217        mMonthPaint.setColor(enabledColor);
218        invalidate();
219    }
220
221    void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) {
222        final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0);
223        mDayOfWeekPaint.setColor(enabledColor);
224        invalidate();
225    }
226
227    void setDayTextColor(ColorStateList dayTextColor) {
228        mDayTextColor = dayTextColor;
229        invalidate();
230    }
231
232    void setDayBackgroundColor(ColorStateList daySelectorColor) {
233        final int activatedColor = daySelectorColor.getColorForState(
234                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
235        mDayBackgroundPaint.setColor(activatedColor);
236        invalidate();
237    }
238
239    @Override
240    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
241        // Workaround for a JB MR1 issue where accessibility delegates on
242        // top-level ListView items are overwritten.
243        if (!mLockAccessibilityDelegate) {
244            super.setAccessibilityDelegate(delegate);
245        }
246    }
247
248    public void setOnDayClickListener(OnDayClickListener listener) {
249        mOnDayClickListener = listener;
250    }
251
252    @Override
253    public boolean dispatchHoverEvent(MotionEvent event) {
254        // First right-of-refusal goes the touch exploration helper.
255        if (mTouchHelper.dispatchHoverEvent(event)) {
256            return true;
257        }
258        return super.dispatchHoverEvent(event);
259    }
260
261    @Override
262    public boolean onTouchEvent(MotionEvent event) {
263        switch (event.getAction()) {
264            case MotionEvent.ACTION_UP:
265                final int day = getDayFromLocation(event.getX(), event.getY());
266                if (day >= 0) {
267                    onDayClick(day);
268                }
269                break;
270        }
271        return true;
272    }
273
274    @Override
275    protected void onDraw(Canvas canvas) {
276        drawMonthTitle(canvas);
277        drawWeekDayLabels(canvas);
278        drawDays(canvas);
279    }
280
281    private static boolean isValidDayOfWeek(int day) {
282        return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY;
283    }
284
285    private static boolean isValidMonth(int month) {
286        return month >= Calendar.JANUARY && month <= Calendar.DECEMBER;
287    }
288
289    /**
290     * Sets all the parameters for displaying this week. Parameters have a default value and
291     * will only update if a new value is included, except for focus month, which will always
292     * default to no focus month if no value is passed in. The only required parameter is the
293     * week start.
294     *
295     * @param selectedDay the selected day of the month, or -1 for no selection.
296     * @param month the month.
297     * @param year the year.
298     * @param weekStart which day the week should start on. {@link Calendar#SUNDAY} through
299     *        {@link Calendar#SATURDAY}.
300     * @param enabledDayStart the first enabled day.
301     * @param enabledDayEnd the last enabled day.
302     */
303    void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
304            int enabledDayEnd) {
305        mActivatedDay = selectedDay;
306
307        if (isValidMonth(month)) {
308            mMonth = month;
309        }
310        mYear = year;
311
312        // Figure out what day today is
313        final Time today = new Time(Time.getCurrentTimezone());
314        today.setToNow();
315        mHasToday = false;
316        mToday = -1;
317
318        mCalendar.set(Calendar.MONTH, mMonth);
319        mCalendar.set(Calendar.YEAR, mYear);
320        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
321        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
322
323        if (isValidDayOfWeek(weekStart)) {
324            mWeekStart = weekStart;
325        } else {
326            mWeekStart = mCalendar.getFirstDayOfWeek();
327        }
328
329        if (enabledDayStart > 0 && enabledDayEnd < 32) {
330            mEnabledDayStart = enabledDayStart;
331        }
332        if (enabledDayEnd > 0 && enabledDayEnd < 32 && enabledDayEnd >= enabledDayStart) {
333            mEnabledDayEnd = enabledDayEnd;
334        }
335
336        mNumCells = getDaysInMonth(mMonth, mYear);
337        for (int i = 0; i < mNumCells; i++) {
338            final int day = i + 1;
339            if (sameDay(day, today)) {
340                mHasToday = true;
341                mToday = day;
342            }
343        }
344        mNumRows = calculateNumRows();
345
346        // Invalidate cached accessibility information.
347        mTouchHelper.invalidateRoot();
348    }
349
350    private static int getDaysInMonth(int month, int year) {
351        switch (month) {
352            case Calendar.JANUARY:
353            case Calendar.MARCH:
354            case Calendar.MAY:
355            case Calendar.JULY:
356            case Calendar.AUGUST:
357            case Calendar.OCTOBER:
358            case Calendar.DECEMBER:
359                return 31;
360            case Calendar.APRIL:
361            case Calendar.JUNE:
362            case Calendar.SEPTEMBER:
363            case Calendar.NOVEMBER:
364                return 30;
365            case Calendar.FEBRUARY:
366                return (year % 4 == 0) ? 29 : 28;
367            default:
368                throw new IllegalArgumentException("Invalid Month");
369        }
370    }
371
372    public void reuse() {
373        mNumRows = DEFAULT_NUM_ROWS;
374        requestLayout();
375    }
376
377    private int calculateNumRows() {
378        int offset = findDayOffset();
379        int dividend = (offset + mNumCells) / mNumDays;
380        int remainder = (offset + mNumCells) % mNumDays;
381        return (dividend + (remainder > 0 ? 1 : 0));
382    }
383
384    private boolean sameDay(int day, Time today) {
385        return mYear == today.year &&
386                mMonth == today.month &&
387                day == today.monthDay;
388    }
389
390    @Override
391    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
392        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
393                + mMonthHeaderHeight);
394    }
395
396    @Override
397    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
398        mWidth = w;
399
400        // Invalidate cached accessibility information.
401        mTouchHelper.invalidateRoot();
402    }
403
404    private String getMonthAndYearString() {
405        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
406                | DateUtils.FORMAT_NO_MONTH_DAY;
407        mStringBuilder.setLength(0);
408        long millis = mCalendar.getTimeInMillis();
409        return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags,
410                Time.getCurrentTimezone()).toString();
411    }
412
413    private void drawMonthTitle(Canvas canvas) {
414        final float x = (mWidth + 2 * mPadding) / 2f;
415        final float y = (mMonthHeaderHeight - mDayOfWeekTextSize) / 2f;
416        canvas.drawText(getMonthAndYearString(), x, y, mMonthPaint);
417    }
418
419    private void drawWeekDayLabels(Canvas canvas) {
420        final int y = mMonthHeaderHeight - (mDayOfWeekTextSize / 2);
421        final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
422
423        for (int i = 0; i < mNumDays; i++) {
424            final int calendarDay = (i + mWeekStart) % mNumDays;
425            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
426
427            final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime());
428            final int x = (2 * i + 1) * dayWidthHalf + mPadding;
429            canvas.drawText(dayLabel, x, y, mDayOfWeekPaint);
430        }
431    }
432
433    /**
434     * Draws the month days.
435     */
436    private void drawDays(Canvas canvas) {
437        final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
438        int y = (((mRowHeight + mDayTextSize) / 2) - DAY_SEPARATOR_WIDTH)
439                + mMonthHeaderHeight;
440        int j = findDayOffset();
441
442        for (int day = 1; day <= mNumCells; day++) {
443            final int x = (2 * j + 1) * dayWidthHalf + mPadding;
444            int stateMask = 0;
445
446            if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
447                stateMask |= StateSet.VIEW_STATE_ENABLED;
448            }
449
450            if (mActivatedDay == day) {
451                stateMask |= StateSet.VIEW_STATE_ACTIVATED;
452
453                canvas.drawCircle(x, y - (mDayTextSize / 3), mRowHeight / 2,
454                        mDayBackgroundPaint);
455            }
456
457            final int[] stateSet = StateSet.get(stateMask);
458            final int dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
459            mDayPaint.setColor(dayTextColor);
460
461            final boolean isDayToday = mHasToday && mToday == day;
462            mDayPaint.setFakeBoldText(isDayToday);
463
464            canvas.drawText(String.format("%d", day), x, y, mDayPaint);
465
466            j++;
467
468            if (j == mNumDays) {
469                j = 0;
470                y += mRowHeight;
471            }
472        }
473    }
474
475    private int findDayOffset() {
476        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
477                - mWeekStart;
478    }
479
480    /**
481     * Calculates the day that the given x position is in, accounting for week
482     * number. Returns the day or -1 if the position wasn't in a day.
483     *
484     * @param x The x position of the touch event
485     * @return The day number, or -1 if the position wasn't in a day
486     */
487    private int getDayFromLocation(float x, float y) {
488        int dayStart = mPadding;
489        if (x < dayStart || x > mWidth - mPadding) {
490            return -1;
491        }
492        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
493        int row = (int) (y - mMonthHeaderHeight) / mRowHeight;
494        int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
495
496        int day = column - findDayOffset() + 1;
497        day += row * mNumDays;
498        if (day < 1 || day > mNumCells) {
499            return -1;
500        }
501        return day;
502    }
503
504    /**
505     * Called when the user clicks on a day. Handles callbacks to the
506     * {@link OnDayClickListener} if one is set.
507     *
508     * @param day The day that was clicked
509     */
510    private void onDayClick(int day) {
511        if (mOnDayClickListener != null) {
512            Calendar date = Calendar.getInstance();
513            date.set(mYear, mMonth, day);
514            mOnDayClickListener.onDayClick(this, date);
515        }
516
517        // This is a no-op if accessibility is turned off.
518        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
519    }
520
521    /**
522     * @return The date that has accessibility focus, or {@code null} if no date
523     *         has focus
524     */
525    Calendar getAccessibilityFocus() {
526        final int day = mTouchHelper.getFocusedVirtualView();
527        Calendar date = null;
528        if (day >= 0) {
529            date = Calendar.getInstance();
530            date.set(mYear, mMonth, day);
531        }
532        return date;
533    }
534
535    /**
536     * Clears accessibility focus within the view. No-op if the view does not
537     * contain accessibility focus.
538     */
539    public void clearAccessibilityFocus() {
540        mTouchHelper.clearFocusedVirtualView();
541    }
542
543    /**
544     * Attempts to restore accessibility focus to the specified date.
545     *
546     * @param day The date which should receive focus
547     * @return {@code false} if the date is not valid for this month view, or
548     *         {@code true} if the date received focus
549     */
550    boolean restoreAccessibilityFocus(Calendar day) {
551        if ((day.get(Calendar.YEAR) != mYear) || (day.get(Calendar.MONTH) != mMonth) ||
552                (day.get(Calendar.DAY_OF_MONTH) > mNumCells)) {
553            return false;
554        }
555        mTouchHelper.setFocusedVirtualView(day.get(Calendar.DAY_OF_MONTH));
556        return true;
557    }
558
559    /**
560     * Provides a virtual view hierarchy for interfacing with an accessibility
561     * service.
562     */
563    private class MonthViewTouchHelper extends ExploreByTouchHelper {
564        private static final String DATE_FORMAT = "dd MMMM yyyy";
565
566        private final Rect mTempRect = new Rect();
567        private final Calendar mTempCalendar = Calendar.getInstance();
568
569        public MonthViewTouchHelper(View host) {
570            super(host);
571        }
572
573        public void setFocusedVirtualView(int virtualViewId) {
574            getAccessibilityNodeProvider(SimpleMonthView.this).performAction(
575                    virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
576        }
577
578        public void clearFocusedVirtualView() {
579            final int focusedVirtualView = getFocusedVirtualView();
580            if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
581                getAccessibilityNodeProvider(SimpleMonthView.this).performAction(
582                        focusedVirtualView,
583                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
584                        null);
585            }
586        }
587
588        @Override
589        protected int getVirtualViewAt(float x, float y) {
590            final int day = getDayFromLocation(x, y);
591            if (day >= 0) {
592                return day;
593            }
594            return ExploreByTouchHelper.INVALID_ID;
595        }
596
597        @Override
598        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
599            for (int day = 1; day <= mNumCells; day++) {
600                virtualViewIds.add(day);
601            }
602        }
603
604        @Override
605        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
606            event.setContentDescription(getItemDescription(virtualViewId));
607        }
608
609        @Override
610        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
611            getItemBounds(virtualViewId, mTempRect);
612
613            node.setContentDescription(getItemDescription(virtualViewId));
614            node.setBoundsInParent(mTempRect);
615            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
616
617            if (virtualViewId == mActivatedDay) {
618                node.setSelected(true);
619            }
620
621        }
622
623        @Override
624        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
625                Bundle arguments) {
626            switch (action) {
627                case AccessibilityNodeInfo.ACTION_CLICK:
628                    onDayClick(virtualViewId);
629                    return true;
630            }
631
632            return false;
633        }
634
635        /**
636         * Calculates the bounding rectangle of a given time object.
637         *
638         * @param day The day to calculate bounds for
639         * @param rect The rectangle in which to store the bounds
640         */
641        private void getItemBounds(int day, Rect rect) {
642            final int offsetX = mPadding;
643            final int offsetY = mMonthHeaderHeight;
644            final int cellHeight = mRowHeight;
645            final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
646            final int index = ((day - 1) + findDayOffset());
647            final int row = (index / mNumDays);
648            final int column = (index % mNumDays);
649            final int x = (offsetX + (column * cellWidth));
650            final int y = (offsetY + (row * cellHeight));
651
652            rect.set(x, y, (x + cellWidth), (y + cellHeight));
653        }
654
655        /**
656         * Generates a description for a given time object. Since this
657         * description will be spoken, the components are ordered by descending
658         * specificity as DAY MONTH YEAR.
659         *
660         * @param day The day to generate a description for
661         * @return A description of the time object
662         */
663        private CharSequence getItemDescription(int day) {
664            mTempCalendar.set(mYear, mMonth, day);
665            final CharSequence date = DateFormat.format(DATE_FORMAT,
666                    mTempCalendar.getTimeInMillis());
667
668            if (day == mActivatedDay) {
669                return getContext().getString(R.string.item_is_selected, date);
670            }
671
672            return date;
673        }
674    }
675
676    /**
677     * Handles callbacks when the user clicks on a time object.
678     */
679    public interface OnDayClickListener {
680        public void onDayClick(SimpleMonthView view, Calendar day);
681    }
682}
683