SimpleMonthView.java revision 816aa142023c75cf427401c21831193998bdf233
1bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio/*
2bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Copyright (C) 2014 The Android Open Source Project
3bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *
4bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License");
5bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * you may not use this file except in compliance with the License.
6bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * You may obtain a copy of the License at
7bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *
8bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *      http://www.apache.org/licenses/LICENSE-2.0
9bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *
10bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Unless required by applicable law or agreed to in writing, software
11bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS,
12bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * See the License for the specific language governing permissions and
14bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * limitations under the License.
15bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */
16bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
17bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Megliopackage android.widget;
18bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
19bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.Context;
20bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.ColorStateList;
21bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.Resources;
220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.content.res.TypedArray;
23bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Canvas;
24bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint;
25bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Align;
26bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Style;
27bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Rect;
28bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Typeface;
2960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viveretteimport android.graphics.drawable.Drawable;
30bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.os.Bundle;
315dc973cb03889c58988703b58aefbd2397fb02c3Alan Viveretteimport android.text.TextPaint;
32bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.text.format.DateFormat;
33bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.util.AttributeSet;
34ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray;
35c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viveretteimport android.util.StateSet;
36bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.MotionEvent;
37bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.View;
38bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityEvent;
39bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
41bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
42bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport com.android.internal.R;
43bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport com.android.internal.widget.ExploreByTouchHelper;
44bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
45fd2dd208d9b0957657589b26c14c48b8e2b7f387Alan Viveretteimport java.text.SimpleDateFormat;
46bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Calendar;
47bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Locale;
48bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
49bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio/**
50bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * A calendar-like view displaying a specified month and the appropriate selectable day numbers
51bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * within the specified month.
52bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */
53bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioclass SimpleMonthView extends View {
540ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private static final int DAYS_IN_WEEK = 7;
550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private static final int MAX_WEEKS_IN_MONTH = 6;
56bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
57bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static final int DEFAULT_SELECTED_DAY = -1;
58bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
59bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private static final String DEFAULT_TITLE_FORMAT = "MMMMy";
610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
62c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
6360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    /** Virtual view ID for previous button. */
6460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private static final int ITEM_ID_PREV = 0x101;
6560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
6660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    /** Virtual view ID for next button. */
6760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private static final int ITEM_ID_NEXT = 0x100;
6860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
695dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette    private final TextPaint mMonthPaint = new TextPaint();
705dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette    private final TextPaint mDayOfWeekPaint = new TextPaint();
715dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette    private final TextPaint mDayPaint = new TextPaint();
720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final Paint mDaySelectorPaint = new Paint();
730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final Paint mDayHighlightPaint = new Paint();
745dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette
750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final Calendar mCalendar = Calendar.getInstance();
7660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private final Calendar mDayOfWeekLabelCalendar = Calendar.getInstance();
77bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final MonthViewTouchHelper mTouchHelper;
79fd2dd208d9b0957657589b26c14c48b8e2b7f387Alan Viverette
800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final SimpleDateFormat mTitleFormatter;
810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final SimpleDateFormat mDayOfWeekFormatter;
82bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
83f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    // Desired dimensions.
84f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredMonthHeight;
85f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredDayOfWeekHeight;
86f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredDayHeight;
87f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredCellWidth;
88f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredDaySelectorRadius;
8960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
9060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    // Next/previous drawables.
9160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private final Drawable mPrevDrawable;
9260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private final Drawable mNextDrawable;
9360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private final Rect mPrevHitArea;
9460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private final Rect mNextHitArea;
9560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private final CharSequence mPrevContentDesc;
9660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private final CharSequence mNextContentDesc;
9760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private CharSequence mTitle;
99bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
100bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mMonth;
101bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mYear;
102bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
103f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    // Dimensions as laid out.
104f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mMonthHeight;
105f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mDayOfWeekHeight;
106f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mDayHeight;
107f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mCellWidth;
108f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mDaySelectorRadius;
109f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
1100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mPaddedWidth;
1110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mPaddedHeight;
112bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The day of month for the selected day, or -1 if no day is selected. */
114c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    private int mActivatedDay = -1;
115bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
1170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * The day of month for today, or -1 if the today is not in the current
1180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * month.
1190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
120bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mToday = DEFAULT_SELECTED_DAY;
121bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The first day of the week (ex. Calendar.SUNDAY). */
123bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mWeekStart = DEFAULT_WEEK_START;
124bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The number of days (ex. 28) in the current month. */
1260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mDaysInMonth;
127bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
1290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * The day of week (ex. Calendar.SUNDAY) for the first day of the current
1300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * month.
1310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
1320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mDayOfWeekStart;
133bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The day of month for the first (inclusive) enabled day. */
135bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mEnabledDayStart = 1;
136bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The day of month for the last (inclusive) enabled day. */
138bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mEnabledDayEnd = 31;
139bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The number of week rows needed to display the current month. */
1410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mNumWeeks = MAX_WEEKS_IN_MONTH;
142bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1430ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** Optional listener for handling day click actions. */
144bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private OnDayClickListener mOnDayClickListener;
145bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
146c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    private ColorStateList mDayTextColor;
147c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
14860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private int mTouchedItem = -1;
14960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
15060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private boolean mPrevEnabled;
15160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private boolean mNextEnabled;
1520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
153bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public SimpleMonthView(Context context) {
154bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        this(context, null);
155bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
156bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
157bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public SimpleMonthView(Context context, AttributeSet attrs) {
158bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        this(context, attrs, R.attr.datePickerStyle);
159bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
160bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
16150eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) {
16250eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        this(context, attrs, defStyleAttr, 0);
16350eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    }
164bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
16550eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
16650eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        super(context, attrs, defStyleAttr, defStyleRes);
167bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
16850eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        final Resources res = context.getResources();
169f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height);
170f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height);
171f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height);
172f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
173f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius);
174bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
17560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mPrevDrawable = context.getDrawable(R.drawable.ic_chevron_left);
17660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mNextDrawable = context.getDrawable(R.drawable.ic_chevron_right);
17760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mPrevHitArea = mPrevDrawable != null ? new Rect() : null;
17860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mNextHitArea = mNextDrawable != null ? new Rect() : null;
17960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mPrevContentDesc = res.getText(R.string.date_picker_prev_month_button);
18060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mNextContentDesc = res.getText(R.string.date_picker_next_month_button);
18160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
182bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Set up accessibility components.
183bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper = new MonthViewTouchHelper(this);
184bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setAccessibilityDelegate(mTouchHelper);
185bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
186bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final Locale locale = res.getConfiguration().locale;
1880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String titleFormat = DateFormat.getBestDateTimePattern(locale, DEFAULT_TITLE_FORMAT);
1890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mTitleFormatter = new SimpleDateFormat(titleFormat, locale);
1900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayOfWeekFormatter = new SimpleDateFormat(DAY_OF_WEEK_FORMAT, locale);
1910ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
1920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        setClickable(true);
1930ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        initPaints(res);
1940ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
1950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
19660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    public void setNextEnabled(boolean enabled) {
19760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mNextEnabled = enabled;
19860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mTouchHelper.invalidateRoot();
19960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        invalidate();
20060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
20160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
20260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    public void setPrevEnabled(boolean enabled) {
20360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mPrevEnabled = enabled;
20460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mTouchHelper.invalidateRoot();
20560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        invalidate();
20660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
20760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
2080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
2090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Applies the specified text appearance resource to a paint, returning the
2100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * text color if one is set in the text appearance.
2110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
2120ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param p the paint to modify
2130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param resId the resource ID of the text appearance
2140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @return the text color, if available
2150ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
2160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private ColorStateList applyTextAppearance(Paint p, int resId) {
2170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final TypedArray ta = mContext.obtainStyledAttributes(null,
2180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.styleable.TextAppearance, 0, resId);
2190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (fontFamily != null) {
2220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            p.setTypeface(Typeface.create(fontFamily, 0));
2230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
2240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        p.setTextSize(ta.getDimensionPixelSize(
2260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.styleable.TextAppearance_textSize, (int) p.getTextSize()));
2270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (textColor != null) {
2300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0);
2310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            p.setColor(enabledColor);
2320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
2330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        ta.recycle();
2350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return textColor;
2370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2390ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setMonthTextAppearance(int resId) {
24060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final ColorStateList monthColor = applyTextAppearance(mMonthPaint, resId);
24160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (monthColor != null) {
24260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (mPrevDrawable != null) {
24360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                mPrevDrawable.setTintList(monthColor);
24460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
24560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (mNextDrawable != null) {
24660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                mNextDrawable.setTintList(monthColor);
24760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
24860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
24960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
2500ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
2510ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2530ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setDayOfWeekTextAppearance(int resId) {
2540ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        applyTextAppearance(mDayOfWeekPaint, resId);
2550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
2560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2570ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setDayTextAppearance(int resId) {
2590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final ColorStateList textColor = applyTextAppearance(mDayPaint, resId);
2600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (textColor != null) {
2610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            mDayTextColor = textColor;
2620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
2630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
2650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public CharSequence getTitle() {
2680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (mTitle == null) {
2690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            mTitle = mTitleFormatter.format(mCalendar.getTime());
2700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
2710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return mTitle;
272c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    }
273c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
274c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    /**
275c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette     * Sets up the text and style properties for painting.
276c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette     */
2770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void initPaints(Resources res) {
2780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String monthTypeface = res.getString(R.string.date_picker_month_typeface);
2790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface);
2800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String dayTypeface = res.getString(R.string.date_picker_day_typeface);
2810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int monthTextSize = res.getDimensionPixelSize(
2830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.dimen.date_picker_month_text_size);
2840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int dayOfWeekTextSize = res.getDimensionPixelSize(
2850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.dimen.date_picker_day_of_week_text_size);
2860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int dayTextSize = res.getDimensionPixelSize(
2870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.dimen.date_picker_day_text_size);
2880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
289c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setAntiAlias(true);
2900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mMonthPaint.setTextSize(monthTextSize);
2910ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0));
292c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setTextAlign(Align.CENTER);
293c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setStyle(Style.FILL);
294c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
295c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setAntiAlias(true);
2960ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayOfWeekPaint.setTextSize(dayOfWeekTextSize);
2970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0));
298c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setTextAlign(Align.CENTER);
299c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setStyle(Style.FILL);
300c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
3010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaySelectorPaint.setAntiAlias(true);
3020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaySelectorPaint.setStyle(Style.FILL);
3030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
3040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayHighlightPaint.setAntiAlias(true);
3050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayHighlightPaint.setStyle(Style.FILL);
306c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
307c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayPaint.setAntiAlias(true);
3080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayPaint.setTextSize(dayTextSize);
3090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayPaint.setTypeface(Typeface.create(dayTypeface, 0));
310c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayPaint.setTextAlign(Align.CENTER);
311c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayPaint.setStyle(Style.FILL);
312bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
313bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
314c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    void setMonthTextColor(ColorStateList monthTextColor) {
315c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0);
316c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setColor(enabledColor);
317c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
318c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    }
319bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
320c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) {
321c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0);
322c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setColor(enabledColor);
323c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
324c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    }
325bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
326c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    void setDayTextColor(ColorStateList dayTextColor) {
327c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayTextColor = dayTextColor;
328c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
329c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    }
330bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
3310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    void setDaySelectorColor(ColorStateList dayBackgroundColor) {
3325dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette        final int activatedColor = dayBackgroundColor.getColorForState(
333c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
3340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaySelectorPaint.setColor(activatedColor);
335c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
336bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
337bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
3380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    void setDayHighlightColor(ColorStateList dayHighlightColor) {
3390ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int pressedColor = dayHighlightColor.getColorForState(
3400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0);
3410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayHighlightPaint.setColor(pressedColor);
3420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
343bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
344bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
345bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public void setOnDayClickListener(OnDayClickListener listener) {
346bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mOnDayClickListener = listener;
347bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
348bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
349bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
350bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public boolean dispatchHoverEvent(MotionEvent event) {
351bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // First right-of-refusal goes the touch exploration helper.
3520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
353bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
354bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
355bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
356bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public boolean onTouchEvent(MotionEvent event) {
35760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int x = (int) (event.getX() + 0.5f);
35860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int y = (int) (event.getY() + 0.5f);
35960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
360bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        switch (event.getAction()) {
3610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_DOWN:
3620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_MOVE:
36360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                final int touchedItem = getItemAtLocation(x, y);
36460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                if (mTouchedItem != touchedItem) {
36560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                    mTouchedItem = touchedItem;
3660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                    invalidate();
367bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
368bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                break;
3690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
3700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_UP:
37160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                final int clickedItem = getItemAtLocation(x, y);
37260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                onItemClicked(clickedItem, true);
3730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // Fall through.
3740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_CANCEL:
3750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // Reset touched day on stream end.
37660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                mTouchedItem = -1;
3770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                invalidate();
3780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                break;
379bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
380bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return true;
381bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
382bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
383bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
384bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected void onDraw(Canvas canvas) {
3850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int paddingLeft = getPaddingLeft();
3860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int paddingTop = getPaddingTop();
3870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        canvas.translate(paddingLeft, paddingTop);
3880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
3890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        drawMonth(canvas);
3900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        drawDaysOfWeek(canvas);
391bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        drawDays(canvas);
39260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        drawButtons(canvas);
3930ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
3940ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        canvas.translate(-paddingLeft, -paddingTop);
3950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
3960ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
3970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void drawMonth(Canvas canvas) {
3980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final float x = mPaddedWidth / 2f;
3990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Vertically centered within the month header height.
4010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent();
4020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final float y = (mMonthHeight - lineHeight) / 2f;
4030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        canvas.drawText(getTitle().toString(), x, y, mMonthPaint);
4050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
4060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void drawDaysOfWeek(Canvas canvas) {
40860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final TextPaint p = mDayOfWeekPaint;
40960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int headerHeight = mMonthHeight;
41060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowHeight = mDayOfWeekHeight;
411f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int colWidth = mCellWidth;
41260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
41360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Text is vertically centered within the day of week height.
41460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
41560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowCenter = headerHeight + rowHeight / 2;
41660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
41760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        for (int col = 0; col < DAYS_IN_WEEK; col++) {
41860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int colCenter = colWidth * col + colWidth / 2;
41960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
42060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final String label = getDayOfWeekLabel(dayOfWeek);
42160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p);
4220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
4230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
4240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
42560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private String getDayOfWeekLabel(int dayOfWeek) {
42660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
42760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        return mDayOfWeekFormatter.format(mDayOfWeekLabelCalendar.getTime());
42860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
42960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
4300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
4310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Draws the month days.
4320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
4330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void drawDays(Canvas canvas) {
43460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final TextPaint p = mDayPaint;
43560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
43660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowHeight = mDayHeight;
437f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int colWidth = mCellWidth;
4380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
43960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Text is vertically centered within the row height.
44060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
44160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        int rowCenter = headerHeight + rowHeight / 2;
4420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
44360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
44460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int colCenter = colWidth * col + colWidth / 2;
4450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            int stateMask = 0;
4460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
4480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                stateMask |= StateSet.VIEW_STATE_ENABLED;
4490ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
4500ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4510ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final boolean isDayActivated = mActivatedDay == day;
4520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            if (isDayActivated) {
4530ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                stateMask |= StateSet.VIEW_STATE_ACTIVATED;
4540ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // Adjust the circle to be centered on the row.
45660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
45760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            } else if (mTouchedItem == day) {
4580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                stateMask |= StateSet.VIEW_STATE_PRESSED;
4590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // Adjust the circle to be centered on the row.
46160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
4620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
4630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
4640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final boolean isDayToday = mToday == day;
4650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final int dayTextColor;
4660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            if (isDayToday && !isDayActivated) {
4670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                dayTextColor = mDaySelectorPaint.getColor();
4680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            } else {
4690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                final int[] stateSet = StateSet.get(stateMask);
4700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
4710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
47260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            p.setColor(dayTextColor);
4730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
47460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p);
4750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
47660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            col++;
4770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
47860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (col == DAYS_IN_WEEK) {
47960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                col = 0;
48060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                rowCenter += rowHeight;
4810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
4820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
483bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
484bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
48560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private void drawButtons(Canvas canvas) {
48660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (mPrevEnabled && mPrevDrawable != null) {
48760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            mPrevDrawable.draw(canvas);
48860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
48960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
49060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (mNextEnabled && mNextDrawable != null) {
49160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            mNextDrawable.draw(canvas);
49260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
49360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
49460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
495518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette    private static boolean isValidDayOfWeek(int day) {
4965878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette        return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY;
4975878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette    }
4985878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette
4995878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette    private static boolean isValidMonth(int month) {
5005878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette        return month >= Calendar.JANUARY && month <= Calendar.DECEMBER;
50175b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio    }
50275b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio
503bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
5040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Sets the selected day.
505bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
5060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param dayOfMonth the selected day of the month, or {@code -1} to clear
5070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *                   the selection
5080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
5090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setSelectedDay(int dayOfMonth) {
5100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mActivatedDay = dayOfMonth;
5110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
5120ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Invalidate cached accessibility information.
5130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mTouchHelper.invalidateRoot();
5140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
5150ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
5160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
5170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
5180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Sets the first day of the week.
5190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
5200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param weekStart which day the week should start on, valid values are
5210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *                  {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
5220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
5230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setFirstDayOfWeek(int weekStart) {
5240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (isValidDayOfWeek(weekStart)) {
5250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            mWeekStart = weekStart;
5260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        } else {
5270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            mWeekStart = mCalendar.getFirstDayOfWeek();
5280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
5290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
5300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Invalidate cached accessibility information.
5310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mTouchHelper.invalidateRoot();
5320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
5330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
5340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
5350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
5360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Sets all the parameters for displaying this week.
5370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * <p>
5380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Parameters have a default value and will only update if a new value is
5390ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * included, except for focus month, which will always default to no focus
5400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * month if no value is passed in. The only required parameter is the week
5410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * start.
5420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
5430ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param selectedDay the selected day of the month, or -1 for no selection
5440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param month the month
5450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param year the year
5460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param weekStart which day the week should start on, valid values are
5470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *                  {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
5480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param enabledDayStart the first enabled day
5490ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param enabledDayEnd the last enabled day
550bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
55175b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio    void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
55275b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            int enabledDayEnd) {
553c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mActivatedDay = selectedDay;
554bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
5555878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette        if (isValidMonth(month)) {
55675b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            mMonth = month;
55775b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio        }
55875b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio        mYear = year;
559bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
560bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mCalendar.set(Calendar.MONTH, mMonth);
561bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mCalendar.set(Calendar.YEAR, mYear);
562bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
563bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
564bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
565518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette        if (isValidDayOfWeek(weekStart)) {
56675b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            mWeekStart = weekStart;
567bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        } else {
568bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mWeekStart = mCalendar.getFirstDayOfWeek();
569bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
570bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
57175b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio        if (enabledDayStart > 0 && enabledDayEnd < 32) {
57275b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            mEnabledDayStart = enabledDayStart;
573bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
57475b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio        if (enabledDayEnd > 0 && enabledDayEnd < 32 && enabledDayEnd >= enabledDayStart) {
57575b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            mEnabledDayEnd = enabledDayEnd;
576bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
577bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
5780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Figure out what day today is.
5790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final Calendar today = Calendar.getInstance();
5800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mToday = -1;
5810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaysInMonth = getDaysInMonth(mMonth, mYear);
5820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        for (int i = 0; i < mDaysInMonth; i++) {
583bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            final int day = i + 1;
584bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (sameDay(day, today)) {
585bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                mToday = day;
586bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
587bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
5880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
5890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Invalidate the old title.
5900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mTitle = null;
591bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
592bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Invalidate cached accessibility information.
593bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper.invalidateRoot();
594bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
595bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
596bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static int getDaysInMonth(int month, int year) {
597bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        switch (month) {
598bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.JANUARY:
599bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.MARCH:
600bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.MAY:
601bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.JULY:
602bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.AUGUST:
603bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.OCTOBER:
604bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.DECEMBER:
605bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return 31;
606bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.APRIL:
607bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.JUNE:
608bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.SEPTEMBER:
609bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.NOVEMBER:
610bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return 30;
611bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.FEBRUARY:
612bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return (year % 4 == 0) ? 29 : 28;
613bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            default:
614bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                throw new IllegalArgumentException("Invalid Month");
615bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
616bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
617bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
6180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private boolean sameDay(int day, Calendar today) {
6190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH)
6200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                && day == today.get(Calendar.DAY_OF_MONTH);
621bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
622bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
623bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
624bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
625816aa142023c75cf427401c21831193998bdf233Alan Viverette        final int preferredHeight = mDesiredDayHeight * MAX_WEEKS_IN_MONTH
626816aa142023c75cf427401c21831193998bdf233Alan Viverette                + mDesiredDayOfWeekHeight + mDesiredMonthHeight
627816aa142023c75cf427401c21831193998bdf233Alan Viverette                + getPaddingTop() + getPaddingBottom();
628f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK
6290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                + getPaddingStart() + getPaddingEnd();
6300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec);
6310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec);
6320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        setMeasuredDimension(resolvedWidth, resolvedHeight);
633bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
634bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
635bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
636f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
637f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        if (!changed) {
638f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette            return;
639f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        }
640f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
641f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // Let's initialize a completely reasonable number of variables.
642f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int w = right - left;
643f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int h = bottom - top;
644f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingLeft = getPaddingLeft();
645f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingTop = getPaddingTop();
646f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingRight = getPaddingRight();
647f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingBottom = getPaddingBottom();
648f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedRight = w - paddingRight;
649f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedBottom = h - paddingBottom;
650f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedWidth = paddedRight - paddingLeft;
651f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedHeight = paddedBottom - paddingTop;
652f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) {
653f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette            return;
654f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        }
655f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
656f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mPaddedWidth = paddedWidth;
657f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mPaddedHeight = paddedHeight;
658f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
659f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // We may have been laid out smaller than our preferred size. If so,
660f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // scale all dimensions to fit.
661f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom;
662f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final float scaleH = paddedHeight / (float) measuredPaddedHeight;
663f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int monthHeight = (int) (mDesiredMonthHeight * scaleH);
66460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
665f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mMonthHeight = monthHeight;
666f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH);
667f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDayHeight = (int) (mDesiredDayHeight * scaleH);
668f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mCellWidth = cellWidth;
669f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
670f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // Compute the largest day selector radius that's still within the clip
671f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // bounds and desired selector radius.
672f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight);
673f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int maxSelectorHeight = mDayHeight / 2 + paddingBottom;
674f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius,
675f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette                Math.min(maxSelectorWidth, maxSelectorHeight));
67660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
67760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Vertically center the previous/next drawables within the month
67860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // header, horizontally center within the day cell, then expand the
67960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // hit area to ensure it's at least 48x48dp.
68060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final Drawable prevDrawable = mPrevDrawable;
68160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (prevDrawable != null) {
68260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int dW = prevDrawable.getIntrinsicWidth();
68360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int dH = prevDrawable.getIntrinsicHeight();
68460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int iconTop = (monthHeight - dH) / 2;
68560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int iconLeft = (cellWidth - dW) / 2;
68660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
68760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            // Button bounds don't include padding, but hit area does.
68860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            prevDrawable.setBounds(iconLeft, iconTop, iconLeft + dW, iconTop + dH);
689f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette            mPrevHitArea.set(0, 0, paddingLeft + cellWidth, paddingTop + monthHeight);
69060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
69160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
69260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final Drawable nextDrawable = mNextDrawable;
69360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (nextDrawable != null) {
69460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int dW = nextDrawable.getIntrinsicWidth();
69560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int dH = nextDrawable.getIntrinsicHeight();
69660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int iconTop = (monthHeight - dH) / 2;
697f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette            final int iconRight = paddedWidth - (cellWidth - dW) / 2;
69860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
69960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            // Button bounds don't include padding, but hit area does.
70060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            nextDrawable.setBounds(iconRight - dW, iconTop, iconRight, iconTop + dH);
701f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette            mNextHitArea.set(paddedRight - cellWidth, 0, w, paddingTop + monthHeight);
70260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
703bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
704bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Invalidate cached accessibility information.
705bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper.invalidateRoot();
706bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
707bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
7080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int findDayOffset() {
7090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int offset = mDayOfWeekStart - mWeekStart;
7100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (mDayOfWeekStart < mWeekStart) {
7110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return offset + DAYS_IN_WEEK;
712bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
7130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return offset;
714bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
715bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
716bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
71760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * Calculates the day of the month or item identifier at the specified
71860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * touch position. Returns the day of the month or -1 if the position
71960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * wasn't in a valid day.
7200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
7210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param x the x position of the touch event
7220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param y the y position of the touch event
72360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * @return the day of the month at (x, y), an item identifier, or -1 if the
72460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     *         position wasn't in a valid day or item
725bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
72660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private int getItemAtLocation(int x, int y) {
72760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (mNextEnabled && mNextDrawable != null && mNextHitArea.contains(x, y)) {
72860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return ITEM_ID_NEXT;
72960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        } else if (mPrevEnabled && mPrevDrawable != null && mPrevHitArea.contains(x, y)) {
73060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return ITEM_ID_PREV;
73160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
73260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
73360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int paddedX = x - getPaddingLeft();
7340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (paddedX < 0 || paddedX >= mPaddedWidth) {
7350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return -1;
7360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
737c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
7380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
73960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int paddedY = y - getPaddingTop();
7400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (paddedY < headerHeight || paddedY >= mPaddedHeight) {
7410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return -1;
7420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
743c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
7440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int row = (paddedY - headerHeight) / mDayHeight;
7450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int col = (paddedX * DAYS_IN_WEEK) / mPaddedWidth;
7460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int index = col + row * DAYS_IN_WEEK;
7470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int day = index + 1 - findDayOffset();
7480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (day < 1 || day > mDaysInMonth) {
7490ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return -1;
750bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
751bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
7520ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return day;
753bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
754bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
755bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
7560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Calculates the bounds of the specified day.
757bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
75860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * @param id the day of the month, or an item identifier
7590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param outBounds the rect to populate with bounds
760bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
76160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private boolean getBoundsForItem(int id, Rect outBounds) {
76260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (mNextEnabled && id == ITEM_ID_NEXT) {
76360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (mNextDrawable != null) {
76460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                outBounds.set(mNextHitArea);
76560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return true;
76660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
76760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        } else if (mPrevEnabled && id == ITEM_ID_PREV) {
76860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (mPrevDrawable != null) {
76960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                outBounds.set(mPrevHitArea);
77060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return true;
77160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
77260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
77360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
77460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (id < 1 || id > mDaysInMonth) {
7750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return false;
776bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
777bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
77860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int index = id - 1 + findDayOffset();
77960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
78060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Compute left edge.
7810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int col = index % DAYS_IN_WEEK;
782f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int colWidth = mCellWidth;
78360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int left = getPaddingLeft() + col * colWidth;
7840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
78560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Compute top edge.
78660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int row = index / DAYS_IN_WEEK;
78760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowHeight = mDayHeight;
7880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
78960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int top = getPaddingTop() + headerHeight + row * rowHeight;
7900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
79160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        outBounds.set(left, top, left + colWidth, top + rowHeight);
7920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return true;
793bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
794bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
795bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
79660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * Called when an item is clicked.
79760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     *
79860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * @param id the day number or item identifier
79960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     */
80060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private boolean onItemClicked(int id, boolean animate) {
80160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        return onNavigationClicked(id, animate) || onDayClicked(id);
80260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
80360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
80460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    /**
805bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Called when the user clicks on a day. Handles callbacks to the
806bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * {@link OnDayClickListener} if one is set.
807bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
8080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param day the day that was clicked
809bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
81060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private boolean onDayClicked(int day) {
81160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (day < 0 || day > mDaysInMonth) {
81260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return false;
81360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
81460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
815bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (mOnDayClickListener != null) {
81660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final Calendar date = Calendar.getInstance();
817bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            date.set(mYear, mMonth, day);
818bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mOnDayClickListener.onDayClick(this, date);
819bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
820bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
821bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // This is a no-op if accessibility is turned off.
822bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
82360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        return true;
82460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
82560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
82660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    /**
82760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * Called when the user clicks on a navigation button. Handles callbacks to
82860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * the {@link OnDayClickListener} if one is set.
82960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     *
83060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     * @param id the item identifier
83160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette     */
83260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private boolean onNavigationClicked(int id, boolean animate) {
83360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int direction;
83460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (id == ITEM_ID_NEXT) {
83560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            direction = 1;
83660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        } else if (id == ITEM_ID_PREV) {
83760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            direction = -1;
83860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        } else {
83960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return false;
84060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
84160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
84260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        if (mOnDayClickListener != null) {
84360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            mOnDayClickListener.onNavigationClick(this, direction, animate);
84460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
84560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
84660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // This is a no-op if accessibility is turned off.
84760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        mTouchHelper.sendEventForVirtualView(id, AccessibilityEvent.TYPE_VIEW_CLICKED);
84860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        return true;
849bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
850bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
851bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
852bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Provides a virtual view hierarchy for interfacing with an accessibility
853bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * service.
854bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
855bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private class MonthViewTouchHelper extends ExploreByTouchHelper {
856bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private static final String DATE_FORMAT = "dd MMMM yyyy";
857bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
858bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private final Rect mTempRect = new Rect();
859bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private final Calendar mTempCalendar = Calendar.getInstance();
860bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
861bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        public MonthViewTouchHelper(View host) {
862bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            super(host);
863bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
864bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
865bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
866bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected int getVirtualViewAt(float x, float y) {
86760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int day = getItemAtLocation((int) (x + 0.5f), (int) (y + 0.5f));
868bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (day >= 0) {
869bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return day;
870bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
871bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            return ExploreByTouchHelper.INVALID_ID;
872bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
873bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
874bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
875ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
87660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (mNextEnabled && mNextDrawable != null) {
87760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                virtualViewIds.add(ITEM_ID_PREV);
87860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
87960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
88060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (mPrevEnabled && mPrevDrawable != null) {
88160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                virtualViewIds.add(ITEM_ID_NEXT);
88260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
88360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
8840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            for (int day = 1; day <= mDaysInMonth; day++) {
885bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                virtualViewIds.add(day);
886bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
887bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
888bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
889bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
890bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
891bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            event.setContentDescription(getItemDescription(virtualViewId));
892bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
893bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
894bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
895bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
89660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final boolean hasBounds = getBoundsForItem(virtualViewId, mTempRect);
8970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
8980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            if (!hasBounds) {
8990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // The day is invalid, kill the node.
9000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                mTempRect.setEmpty();
9010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                node.setContentDescription("");
9020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                node.setBoundsInParent(mTempRect);
9030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                node.setVisibleToUser(false);
9040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                return;
9050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
906bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
90760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            node.setText(getItemText(virtualViewId));
908bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            node.setContentDescription(getItemDescription(virtualViewId));
909bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            node.setBoundsInParent(mTempRect);
9100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            node.addAction(AccessibilityAction.ACTION_CLICK);
911bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
912c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette            if (virtualViewId == mActivatedDay) {
91360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                // TODO: This should use activated once that's supported.
91460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                node.setChecked(true);
915bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
916bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
917bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
918bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
919bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
920bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
921bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                Bundle arguments) {
922bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            switch (action) {
923bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                case AccessibilityNodeInfo.ACTION_CLICK:
92460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                    return onItemClicked(virtualViewId, false);
925bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
926bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
927bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            return false;
928bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
929bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
930bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        /**
93160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * Generates a description for a given virtual view.
932bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         *
93360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * @param id the day or item identifier to generate a description for
93460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * @return a description of the virtual view
935bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         */
93660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        private CharSequence getItemDescription(int id) {
93760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (id == ITEM_ID_NEXT) {
93860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return mNextContentDesc;
93960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            } else if (id == ITEM_ID_PREV) {
94060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return mPrevContentDesc;
94160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            } else if (id >= 1 && id <= mDaysInMonth) {
94260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                mTempCalendar.set(mYear, mMonth, id);
94360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis());
94460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
945bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
94660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return "";
94760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
94860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
94960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        /**
95060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * Generates displayed text for a given virtual view.
95160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         *
95260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * @param id the day or item identifier to generate text for
95360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * @return the visible text of the virtual view
95460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         */
95560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        private CharSequence getItemText(int id) {
95660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (id == ITEM_ID_NEXT || id == ITEM_ID_PREV) {
95760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return null;
95860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            } else if (id >= 1 && id <= mDaysInMonth) {
95960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return Integer.toString(id);
960bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
961bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
96260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return null;
963bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
964bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
965bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
966bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
967bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Handles callbacks when the user clicks on a time object.
968bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
969bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public interface OnDayClickListener {
970bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        public void onDayClick(SimpleMonthView view, Calendar day);
97160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        public void onNavigationClick(SimpleMonthView view, int direction, boolean animate);
972bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
973bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio}
974