1/*
2 * Copyright (C) 2010 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 com.android.internal.R;
20
21import android.annotation.AttrRes;
22import android.annotation.ColorInt;
23import android.annotation.DrawableRes;
24import android.annotation.NonNull;
25import android.annotation.Nullable;
26import android.annotation.StyleRes;
27import android.annotation.Widget;
28import android.content.Context;
29import android.content.res.Configuration;
30import android.content.res.TypedArray;
31import android.graphics.drawable.Drawable;
32import android.icu.util.Calendar;
33import android.icu.util.TimeZone;
34import android.util.AttributeSet;
35import android.util.Log;
36
37import java.text.DateFormat;
38import java.text.ParseException;
39import java.text.SimpleDateFormat;
40import java.util.Date;
41import java.util.Locale;
42
43/**
44 * This class is a calendar widget for displaying and selecting dates. The
45 * range of dates supported by this calendar is configurable.
46 * <p>
47 * The exact appearance and interaction model of this widget may vary between
48 * OS versions and themes (e.g. Holo versus Material), but in general a user
49 * can select a date by tapping on it and can scroll or fling the calendar to a
50 * desired date.
51 *
52 * @attr ref android.R.styleable#CalendarView_showWeekNumber
53 * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
54 * @attr ref android.R.styleable#CalendarView_minDate
55 * @attr ref android.R.styleable#CalendarView_maxDate
56 * @attr ref android.R.styleable#CalendarView_shownWeekCount
57 * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
58 * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
59 * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
60 * @attr ref android.R.styleable#CalendarView_weekNumberColor
61 * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
62 * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
63 * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
64 * @attr ref android.R.styleable#CalendarView_dateTextAppearance
65 */
66@Widget
67public class CalendarView extends FrameLayout {
68    private static final String LOG_TAG = "CalendarView";
69
70    private static final int MODE_HOLO = 0;
71    private static final int MODE_MATERIAL = 1;
72
73    private final CalendarViewDelegate mDelegate;
74
75    /**
76     * The callback used to indicate the user changes the date.
77     */
78    public interface OnDateChangeListener {
79
80        /**
81         * Called upon change of the selected day.
82         *
83         * @param view The view associated with this listener.
84         * @param year The year that was set.
85         * @param month The month that was set [0-11].
86         * @param dayOfMonth The day of the month that was set.
87         */
88        void onSelectedDayChange(@NonNull CalendarView view, int year, int month, int dayOfMonth);
89    }
90
91    public CalendarView(@NonNull Context context) {
92        this(context, null);
93    }
94
95    public CalendarView(@NonNull Context context, @Nullable AttributeSet attrs) {
96        this(context, attrs, R.attr.calendarViewStyle);
97    }
98
99    public CalendarView(@NonNull Context context, @Nullable AttributeSet attrs,
100            @AttrRes int defStyleAttr) {
101        this(context, attrs, defStyleAttr, 0);
102    }
103
104    public CalendarView(@NonNull Context context, @Nullable AttributeSet attrs,
105            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
106        super(context, attrs, defStyleAttr, defStyleRes);
107
108        final TypedArray a = context.obtainStyledAttributes(
109                attrs, R.styleable.CalendarView, defStyleAttr, defStyleRes);
110        final int mode = a.getInt(R.styleable.CalendarView_calendarViewMode, MODE_HOLO);
111        a.recycle();
112
113        switch (mode) {
114            case MODE_HOLO:
115                mDelegate = new CalendarViewLegacyDelegate(
116                        this, context, attrs, defStyleAttr, defStyleRes);
117                break;
118            case MODE_MATERIAL:
119                mDelegate = new CalendarViewMaterialDelegate(
120                        this, context, attrs, defStyleAttr, defStyleRes);
121                break;
122            default:
123                throw new IllegalArgumentException("invalid calendarViewMode attribute");
124        }
125    }
126
127    /**
128     * Sets the number of weeks to be shown.
129     *
130     * @param count The shown week count.
131     *
132     * @attr ref android.R.styleable#CalendarView_shownWeekCount
133     * @deprecated No longer used by Material-style CalendarView.
134     */
135    @Deprecated
136    public void setShownWeekCount(int count) {
137        mDelegate.setShownWeekCount(count);
138    }
139
140    /**
141     * Gets the number of weeks to be shown.
142     *
143     * @return The shown week count.
144     *
145     * @attr ref android.R.styleable#CalendarView_shownWeekCount
146     * @deprecated No longer used by Material-style CalendarView.
147     */
148    @Deprecated
149    public int getShownWeekCount() {
150        return mDelegate.getShownWeekCount();
151    }
152
153    /**
154     * Sets the background color for the selected week.
155     *
156     * @param color The week background color.
157     *
158     * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
159     * @deprecated No longer used by Material-style CalendarView.
160     */
161    @Deprecated
162    public void setSelectedWeekBackgroundColor(@ColorInt int color) {
163        mDelegate.setSelectedWeekBackgroundColor(color);
164    }
165
166    /**
167     * Gets the background color for the selected week.
168     *
169     * @return The week background color.
170     *
171     * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
172     * @deprecated No longer used by Material-style CalendarView.
173     */
174    @ColorInt
175    @Deprecated
176    public int getSelectedWeekBackgroundColor() {
177        return mDelegate.getSelectedWeekBackgroundColor();
178    }
179
180    /**
181     * Sets the color for the dates of the focused month.
182     *
183     * @param color The focused month date color.
184     *
185     * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
186     * @deprecated No longer used by Material-style CalendarView.
187     */
188    @Deprecated
189    public void setFocusedMonthDateColor(@ColorInt int color) {
190        mDelegate.setFocusedMonthDateColor(color);
191    }
192
193    /**
194     * Gets the color for the dates in the focused month.
195     *
196     * @return The focused month date color.
197     *
198     * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
199     * @deprecated No longer used by Material-style CalendarView.
200     */
201    @ColorInt
202    @Deprecated
203    public int getFocusedMonthDateColor() {
204        return mDelegate.getFocusedMonthDateColor();
205    }
206
207    /**
208     * Sets the color for the dates of a not focused month.
209     *
210     * @param color A not focused month date color.
211     *
212     * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
213     * @deprecated No longer used by Material-style CalendarView.
214     */
215    @Deprecated
216    public void setUnfocusedMonthDateColor(@ColorInt int color) {
217        mDelegate.setUnfocusedMonthDateColor(color);
218    }
219
220    /**
221     * Gets the color for the dates in a not focused month.
222     *
223     * @return A not focused month date color.
224     *
225     * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
226     * @deprecated No longer used by Material-style CalendarView.
227     */
228    @ColorInt
229    @Deprecated
230    public int getUnfocusedMonthDateColor() {
231        return mDelegate.getUnfocusedMonthDateColor();
232    }
233
234    /**
235     * Sets the color for the week numbers.
236     *
237     * @param color The week number color.
238     *
239     * @attr ref android.R.styleable#CalendarView_weekNumberColor
240     * @deprecated No longer used by Material-style CalendarView.
241     */
242    @Deprecated
243    public void setWeekNumberColor(@ColorInt int color) {
244        mDelegate.setWeekNumberColor(color);
245    }
246
247    /**
248     * Gets the color for the week numbers.
249     *
250     * @return The week number color.
251     *
252     * @attr ref android.R.styleable#CalendarView_weekNumberColor
253     * @deprecated No longer used by Material-style CalendarView.
254     */
255    @ColorInt
256    @Deprecated
257    public int getWeekNumberColor() {
258        return mDelegate.getWeekNumberColor();
259    }
260
261    /**
262     * Sets the color for the separator line between weeks.
263     *
264     * @param color The week separator color.
265     *
266     * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
267     * @deprecated No longer used by Material-style CalendarView.
268     */
269    @Deprecated
270    public void setWeekSeparatorLineColor(@ColorInt int color) {
271        mDelegate.setWeekSeparatorLineColor(color);
272    }
273
274    /**
275     * Gets the color for the separator line between weeks.
276     *
277     * @return The week separator color.
278     *
279     * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
280     * @deprecated No longer used by Material-style CalendarView.
281     */
282    @ColorInt
283    @Deprecated
284    public int getWeekSeparatorLineColor() {
285        return mDelegate.getWeekSeparatorLineColor();
286    }
287
288    /**
289     * Sets the drawable for the vertical bar shown at the beginning and at
290     * the end of the selected date.
291     *
292     * @param resourceId The vertical bar drawable resource id.
293     *
294     * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
295     * @deprecated No longer used by Material-style CalendarView.
296     */
297    @Deprecated
298    public void setSelectedDateVerticalBar(@DrawableRes int resourceId) {
299        mDelegate.setSelectedDateVerticalBar(resourceId);
300    }
301
302    /**
303     * Sets the drawable for the vertical bar shown at the beginning and at
304     * the end of the selected date.
305     *
306     * @param drawable The vertical bar drawable.
307     *
308     * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
309     * @deprecated No longer used by Material-style CalendarView.
310     */
311    @Deprecated
312    public void setSelectedDateVerticalBar(Drawable drawable) {
313        mDelegate.setSelectedDateVerticalBar(drawable);
314    }
315
316    /**
317     * Gets the drawable for the vertical bar shown at the beginning and at
318     * the end of the selected date.
319     *
320     * @return The vertical bar drawable.
321     * @deprecated No longer used by Material-style CalendarView.
322     */
323    @Deprecated
324    public Drawable getSelectedDateVerticalBar() {
325        return mDelegate.getSelectedDateVerticalBar();
326    }
327
328    /**
329     * Sets the text appearance for the week day abbreviation of the calendar header.
330     *
331     * @param resourceId The text appearance resource id.
332     *
333     * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
334     */
335    public void setWeekDayTextAppearance(@StyleRes int resourceId) {
336        mDelegate.setWeekDayTextAppearance(resourceId);
337    }
338
339    /**
340     * Gets the text appearance for the week day abbreviation of the calendar header.
341     *
342     * @return The text appearance resource id.
343     *
344     * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
345     */
346    public @StyleRes int getWeekDayTextAppearance() {
347        return mDelegate.getWeekDayTextAppearance();
348    }
349
350    /**
351     * Sets the text appearance for the calendar dates.
352     *
353     * @param resourceId The text appearance resource id.
354     *
355     * @attr ref android.R.styleable#CalendarView_dateTextAppearance
356     */
357    public void setDateTextAppearance(@StyleRes int resourceId) {
358        mDelegate.setDateTextAppearance(resourceId);
359    }
360
361    /**
362     * Gets the text appearance for the calendar dates.
363     *
364     * @return The text appearance resource id.
365     *
366     * @attr ref android.R.styleable#CalendarView_dateTextAppearance
367     */
368    public @StyleRes int getDateTextAppearance() {
369        return mDelegate.getDateTextAppearance();
370    }
371
372    /**
373     * Gets the minimal date supported by this {@link CalendarView} in milliseconds
374     * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
375     * zone.
376     * <p>
377     * Note: The default minimal date is 01/01/1900.
378     * <p>
379     *
380     * @return The minimal supported date.
381     *
382     * @attr ref android.R.styleable#CalendarView_minDate
383     */
384    public long getMinDate() {
385        return mDelegate.getMinDate();
386    }
387
388    /**
389     * Sets the minimal date supported by this {@link CalendarView} in milliseconds
390     * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
391     * zone.
392     *
393     * @param minDate The minimal supported date.
394     *
395     * @attr ref android.R.styleable#CalendarView_minDate
396     */
397    public void setMinDate(long minDate) {
398        mDelegate.setMinDate(minDate);
399    }
400
401    /**
402     * Gets the maximal date supported by this {@link CalendarView} in milliseconds
403     * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
404     * zone.
405     * <p>
406     * Note: The default maximal date is 01/01/2100.
407     * <p>
408     *
409     * @return The maximal supported date.
410     *
411     * @attr ref android.R.styleable#CalendarView_maxDate
412     */
413    public long getMaxDate() {
414        return mDelegate.getMaxDate();
415    }
416
417    /**
418     * Sets the maximal date supported by this {@link CalendarView} in milliseconds
419     * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time
420     * zone.
421     *
422     * @param maxDate The maximal supported date.
423     *
424     * @attr ref android.R.styleable#CalendarView_maxDate
425     */
426    public void setMaxDate(long maxDate) {
427        mDelegate.setMaxDate(maxDate);
428    }
429
430    /**
431     * Sets whether to show the week number.
432     *
433     * @param showWeekNumber True to show the week number.
434     * @deprecated No longer used by Material-style CalendarView.
435     *
436     * @attr ref android.R.styleable#CalendarView_showWeekNumber
437     */
438    @Deprecated
439    public void setShowWeekNumber(boolean showWeekNumber) {
440        mDelegate.setShowWeekNumber(showWeekNumber);
441    }
442
443    /**
444     * Gets whether to show the week number.
445     *
446     * @return True if showing the week number.
447     * @deprecated No longer used by Material-style CalendarView.
448     *
449     * @attr ref android.R.styleable#CalendarView_showWeekNumber
450     */
451    @Deprecated
452    public boolean getShowWeekNumber() {
453        return mDelegate.getShowWeekNumber();
454    }
455
456    /**
457     * Gets the first day of week.
458     *
459     * @return The first day of the week conforming to the {@link CalendarView}
460     *         APIs.
461     * @see Calendar#MONDAY
462     * @see Calendar#TUESDAY
463     * @see Calendar#WEDNESDAY
464     * @see Calendar#THURSDAY
465     * @see Calendar#FRIDAY
466     * @see Calendar#SATURDAY
467     * @see Calendar#SUNDAY
468     *
469     * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
470     */
471    public int getFirstDayOfWeek() {
472        return mDelegate.getFirstDayOfWeek();
473    }
474
475    /**
476     * Sets the first day of week.
477     *
478     * @param firstDayOfWeek The first day of the week conforming to the
479     *            {@link CalendarView} APIs.
480     * @see Calendar#MONDAY
481     * @see Calendar#TUESDAY
482     * @see Calendar#WEDNESDAY
483     * @see Calendar#THURSDAY
484     * @see Calendar#FRIDAY
485     * @see Calendar#SATURDAY
486     * @see Calendar#SUNDAY
487     *
488     * @attr ref android.R.styleable#CalendarView_firstDayOfWeek
489     */
490    public void setFirstDayOfWeek(int firstDayOfWeek) {
491        mDelegate.setFirstDayOfWeek(firstDayOfWeek);
492    }
493
494    /**
495     * Sets the listener to be notified upon selected date change.
496     *
497     * @param listener The listener to be notified.
498     */
499    public void setOnDateChangeListener(OnDateChangeListener listener) {
500        mDelegate.setOnDateChangeListener(listener);
501    }
502
503    /**
504     * Gets the selected date in milliseconds since January 1, 1970 00:00:00 in
505     * {@link TimeZone#getDefault()} time zone.
506     *
507     * @return The selected date.
508     */
509    public long getDate() {
510        return mDelegate.getDate();
511    }
512
513    /**
514     * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
515     * {@link TimeZone#getDefault()} time zone.
516     *
517     * @param date The selected date.
518     *
519     * @throws IllegalArgumentException of the provided date is before the
520     *        minimal or after the maximal date.
521     *
522     * @see #setDate(long, boolean, boolean)
523     * @see #setMinDate(long)
524     * @see #setMaxDate(long)
525     */
526    public void setDate(long date) {
527        mDelegate.setDate(date);
528    }
529
530    /**
531     * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in
532     * {@link TimeZone#getDefault()} time zone.
533     *
534     * @param date The date.
535     * @param animate Whether to animate the scroll to the current date.
536     * @param center Whether to center the current date even if it is already visible.
537     *
538     * @throws IllegalArgumentException of the provided date is before the
539     *        minimal or after the maximal date.
540     *
541     * @see #setMinDate(long)
542     * @see #setMaxDate(long)
543     */
544    public void setDate(long date, boolean animate, boolean center) {
545        mDelegate.setDate(date, animate, center);
546    }
547
548    @Override
549    protected void onConfigurationChanged(Configuration newConfig) {
550        super.onConfigurationChanged(newConfig);
551        mDelegate.onConfigurationChanged(newConfig);
552    }
553
554    @Override
555    public CharSequence getAccessibilityClassName() {
556        return CalendarView.class.getName();
557    }
558
559    /**
560     * A delegate interface that defined the public API of the CalendarView. Allows different
561     * CalendarView implementations. This would need to be implemented by the CalendarView delegates
562     * for the real behavior.
563     */
564    private interface CalendarViewDelegate {
565        void setShownWeekCount(int count);
566        int getShownWeekCount();
567
568        void setSelectedWeekBackgroundColor(@ColorInt int color);
569        @ColorInt int getSelectedWeekBackgroundColor();
570
571        void setFocusedMonthDateColor(@ColorInt int color);
572        @ColorInt int getFocusedMonthDateColor();
573
574        void setUnfocusedMonthDateColor(@ColorInt int color);
575        @ColorInt int getUnfocusedMonthDateColor();
576
577        void setWeekNumberColor(@ColorInt int color);
578        @ColorInt int getWeekNumberColor();
579
580        void setWeekSeparatorLineColor(@ColorInt int color);
581        @ColorInt int getWeekSeparatorLineColor();
582
583        void setSelectedDateVerticalBar(@DrawableRes int resourceId);
584        void setSelectedDateVerticalBar(Drawable drawable);
585        Drawable getSelectedDateVerticalBar();
586
587        void setWeekDayTextAppearance(@StyleRes int resourceId);
588        @StyleRes int getWeekDayTextAppearance();
589
590        void setDateTextAppearance(@StyleRes int resourceId);
591        @StyleRes int getDateTextAppearance();
592
593        void setMinDate(long minDate);
594        long getMinDate();
595
596        void setMaxDate(long maxDate);
597        long getMaxDate();
598
599        void setShowWeekNumber(boolean showWeekNumber);
600        boolean getShowWeekNumber();
601
602        void setFirstDayOfWeek(int firstDayOfWeek);
603        int getFirstDayOfWeek();
604
605        void setDate(long date);
606        void setDate(long date, boolean animate, boolean center);
607        long getDate();
608
609        void setOnDateChangeListener(OnDateChangeListener listener);
610
611        void onConfigurationChanged(Configuration newConfig);
612    }
613
614    /**
615     * An abstract class which can be used as a start for CalendarView implementations
616     */
617    abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate {
618        /** The default minimal date. */
619        protected static final String DEFAULT_MIN_DATE = "01/01/1900";
620
621        /** The default maximal date. */
622        protected static final String DEFAULT_MAX_DATE = "01/01/2100";
623
624        protected CalendarView mDelegator;
625        protected Context mContext;
626        protected Locale mCurrentLocale;
627
628        AbstractCalendarViewDelegate(CalendarView delegator, Context context) {
629            mDelegator = delegator;
630            mContext = context;
631
632            // Initialization based on locale
633            setCurrentLocale(Locale.getDefault());
634        }
635
636        protected void setCurrentLocale(Locale locale) {
637            if (locale.equals(mCurrentLocale)) {
638                return;
639            }
640            mCurrentLocale = locale;
641        }
642
643        @Override
644        public void setShownWeekCount(int count) {
645            // Deprecated.
646        }
647
648        @Override
649        public int getShownWeekCount() {
650            // Deprecated.
651            return 0;
652        }
653
654        @Override
655        public void setSelectedWeekBackgroundColor(@ColorInt int color) {
656            // Deprecated.
657        }
658
659        @ColorInt
660        @Override
661        public int getSelectedWeekBackgroundColor() {
662            return 0;
663        }
664
665        @Override
666        public void setFocusedMonthDateColor(@ColorInt int color) {
667            // Deprecated.
668        }
669
670        @ColorInt
671        @Override
672        public int getFocusedMonthDateColor() {
673            return 0;
674        }
675
676        @Override
677        public void setUnfocusedMonthDateColor(@ColorInt int color) {
678            // Deprecated.
679        }
680
681        @ColorInt
682        @Override
683        public int getUnfocusedMonthDateColor() {
684            return 0;
685        }
686
687        @Override
688        public void setWeekNumberColor(@ColorInt int color) {
689            // Deprecated.
690        }
691
692        @ColorInt
693        @Override
694        public int getWeekNumberColor() {
695            // Deprecated.
696            return 0;
697        }
698
699        @Override
700        public void setWeekSeparatorLineColor(@ColorInt int color) {
701            // Deprecated.
702        }
703
704        @ColorInt
705        @Override
706        public int getWeekSeparatorLineColor() {
707            // Deprecated.
708            return 0;
709        }
710
711        @Override
712        public void setSelectedDateVerticalBar(@DrawableRes int resId) {
713            // Deprecated.
714        }
715
716        @Override
717        public void setSelectedDateVerticalBar(Drawable drawable) {
718            // Deprecated.
719        }
720
721        @Override
722        public Drawable getSelectedDateVerticalBar() {
723            // Deprecated.
724            return null;
725        }
726
727        @Override
728        public void setShowWeekNumber(boolean showWeekNumber) {
729            // Deprecated.
730        }
731
732        @Override
733        public boolean getShowWeekNumber() {
734            // Deprecated.
735            return false;
736        }
737
738        @Override
739        public void onConfigurationChanged(Configuration newConfig) {
740            // Nothing to do here, configuration changes are already propagated
741            // by ViewGroup.
742        }
743    }
744
745    /** String for parsing dates. */
746    private static final String DATE_FORMAT = "MM/dd/yyyy";
747
748    /** Date format for parsing dates. */
749    private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT);
750
751    /**
752     * Utility method for the date format used by CalendarView's min/max date.
753     *
754     * @hide Use only as directed. For internal use only.
755     */
756    public static boolean parseDate(String date, Calendar outDate) {
757        if (date == null || date.isEmpty()) {
758            return false;
759        }
760
761        try {
762            final Date parsedDate = DATE_FORMATTER.parse(date);
763            outDate.setTime(parsedDate);
764            return true;
765        } catch (ParseException e) {
766            Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
767            return false;
768        }
769    }
770}
771