1e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung/*
2e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * Copyright (C) 2013 The Android Open Source Project
3e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung *
4e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * Licensed under the Apache License, Version 2.0 (the "License");
5e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * you may not use this file except in compliance with the License.
6e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * You may obtain a copy of the License at
7e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung *
8e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung *      http://www.apache.org/licenses/LICENSE-2.0
9e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung *
10e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * Unless required by applicable law or agreed to in writing, software
11e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * distributed under the License is distributed on an "AS IS" BASIS,
12e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * See the License for the specific language governing permissions and
14e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * limitations under the License.
15e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung */
16e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
17e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungpackage com.android.datetimepicker.date;
18e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
19e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.content.Context;
20e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.content.res.Resources;
21e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.graphics.Canvas;
22e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.graphics.Paint;
23e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.graphics.Paint.Align;
24e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.graphics.Paint.Style;
25e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.graphics.Rect;
26e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.graphics.Typeface;
27e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.os.Bundle;
28e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.support.v4.view.ViewCompat;
29e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
30e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.support.v4.widget.ExploreByTouchHelper;
31e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.text.format.DateFormat;
32e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.text.format.DateUtils;
33e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.text.format.Time;
34fc0e80c929547bba62f93d609c83fe6d6bfbfd34Rubenimport android.util.AttributeSet;
35e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.MotionEvent;
36e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.View;
37e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.accessibility.AccessibilityEvent;
38e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.accessibility.AccessibilityNodeInfo;
39e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
40e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport com.android.datetimepicker.R;
41e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport com.android.datetimepicker.Utils;
42e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport com.android.datetimepicker.date.MonthAdapter.CalendarDay;
43e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
44e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.security.InvalidParameterException;
45e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.Calendar;
46e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.Formatter;
47e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.HashMap;
48e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.List;
49e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.Locale;
50e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
51e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung/**
52e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * A calendar-like view displaying a specified month and the appropriate selectable day numbers
53e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * within the specified month.
54e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung */
55e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungpublic abstract class MonthView extends View {
56e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private static final String TAG = "MonthView";
57e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
58e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
59e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * These params can be passed into the view to control how it appears.
60e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
61e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * values are unlikely to fit most layouts correctly.
62e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
63e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
64e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This sets the height of this week in pixels
65e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
66e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_HEIGHT = "height";
67e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
68e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This specifies the position (or weeks since the epoch) of this week,
69e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
70e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
71e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_MONTH = "month";
72e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
73e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This specifies the position (or weeks since the epoch) of this week,
74e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
75e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
76e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_YEAR = "year";
77e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
78e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This sets one of the days in this view as selected {@link Time#SUNDAY}
79e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * through {@link Time#SATURDAY}.
80e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
81e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
82e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
83e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Which day the week should start on. {@link Time#SUNDAY} through
84e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link Time#SATURDAY}.
85e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
86e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_WEEK_START = "week_start";
87e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
88e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * How many days to display at a time. Days will be displayed starting with
89e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #mWeekStart}.
90e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
91e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
92e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
93e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Which month is currently in focus, as defined by {@link Time#month}
94e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * [0-11].
95e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
96e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
97e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
98e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * If this month should display week numbers. false if 0, true otherwise.
99e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
100e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
101e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
102e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int DEFAULT_HEIGHT = 32;
103e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MIN_HEIGHT = 10;
104e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_SELECTED_DAY = -1;
105e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
106e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_NUM_DAYS = 7;
107e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_SHOW_WK_NUM = 0;
108e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_FOCUS_MONTH = -1;
109e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_NUM_ROWS = 6;
110e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int MAX_NUM_ROWS = 6;
111e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
112e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private static final int SELECTED_CIRCLE_ALPHA = 60;
113e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
114e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int DAY_SEPARATOR_WIDTH = 1;
115e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MINI_DAY_NUMBER_TEXT_SIZE;
116e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MONTH_LABEL_TEXT_SIZE;
117e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MONTH_DAY_LABEL_TEXT_SIZE;
118e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MONTH_HEADER_SIZE;
119e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int DAY_SELECTED_CIRCLE_SIZE;
120e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
121e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // used for scaling to the device density
122e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static float mScale = 0;
123e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
124fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben    protected DatePickerController mController;
1257c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
126e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // affects the padding on the sides of this view
127d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein    protected int mEdgePadding = 0;
128e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
129e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String mDayOfWeekTypeface;
130e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String mMonthTitleTypeface;
131e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
132e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthNumPaint;
133e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthTitlePaint;
134e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthTitleBGPaint;
135e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mSelectedCirclePaint;
136e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthDayLabelPaint;
137e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
138e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final Formatter mFormatter;
139e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final StringBuilder mStringBuilder;
140e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
141e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The Julian day of the first day displayed by this item
142e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mFirstJulianDay = -1;
143e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The month of the first day in this week
144e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mFirstMonth = -1;
145e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The month of the last day in this week
146e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mLastMonth = -1;
147e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
148e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonth;
149e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
150e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mYear;
151e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Quick reference to the width of this view, matches parent
152e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mWidth;
153e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The height this view should draw at in pixels, set by height param
154e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mRowHeight = DEFAULT_HEIGHT;
155e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // If this view contains the today
156e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected boolean mHasToday = false;
157e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day is selected [0-6] or -1 if no day is selected
158e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedDay = -1;
159e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day is today [0-6] or -1 if no day is today
160e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mToday = DEFAULT_SELECTED_DAY;
161e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day of the week to start on [0-6]
162e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mWeekStart = DEFAULT_WEEK_START;
163e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // How many days to display
164e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mNumDays = DEFAULT_NUM_DAYS;
165e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The number of days + a spot for week number if it is displayed
166e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mNumCells = mNumDays;
167e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The left edge of the selected day
168e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedLeft = -1;
169e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The right edge of the selected day
170e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedRight = -1;
171e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
172e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final Calendar mCalendar;
1736286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected final Calendar mDayLabelCalendar;
174e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final MonthViewTouchHelper mTouchHelper;
175e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
176d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein    protected int mNumRows = DEFAULT_NUM_ROWS;
177e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
178e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Optional listener for handling day click actions
17959d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben    protected OnDayClickListener mOnDayClickListener;
18059d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben
181e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Whether to prevent setting the accessibility delegate
182e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private boolean mLockAccessibilityDelegate;
183e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
184e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mDayTextColor;
185e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mTodayNumberColor;
1867c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    protected int mDisabledDayTextColor;
187e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonthTitleColor;
188e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonthTitleBGColor;
189e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
190e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public MonthView(Context context) {
1917c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        this(context, null);
1927c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
1937c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
194fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben    public MonthView(Context context, AttributeSet attr) {
195fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben        super(context, attr);
196e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        Resources res = context.getResources();
197e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
198e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayLabelCalendar = Calendar.getInstance();
199e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar = Calendar.getInstance();
200e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
201e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
202e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleTypeface = res.getString(R.string.sans_serif);
203e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
204e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayTextColor = res.getColor(R.color.date_picker_text_normal);
205e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTodayNumberColor = res.getColor(R.color.blue);
2067c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        mDisabledDayTextColor = res.getColor(R.color.date_picker_text_disabled);
207a09b3c940e98b8606a16a94b48b6d0121d9d3635Scott Kennedy        mMonthTitleColor = res.getColor(android.R.color.white);
208e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGColor = res.getColor(R.color.circle_background);
209e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
210e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mStringBuilder = new StringBuilder(50);
211e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
212e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
213e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.day_number_size);
214e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_label_size);
215e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_day_label_text_size);
216e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.month_list_item_header_height);
217e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        DAY_SELECTED_CIRCLE_SIZE = res
218e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                .getDimensionPixelSize(R.dimen.day_number_select_circle_radius);
219e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
220e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mRowHeight = (res.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height)
221fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben                - getMonthHeaderSize()) / MAX_NUM_ROWS;
222e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
223e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Set up accessibility components.
224529542558544a0e6d78424e33c60934adc143804Ruben        mTouchHelper = getMonthViewTouchHelper();
225e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
226e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
227e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mLockAccessibilityDelegate = true;
228e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
229e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Sets up any standard paints that will be used
230e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        initView();
231e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
232e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
233fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben    public void setDatePickerController(DatePickerController controller) {
234fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben        mController = controller;
235fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben    }
236fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben
237529542558544a0e6d78424e33c60934adc143804Ruben    protected MonthViewTouchHelper getMonthViewTouchHelper() {
238529542558544a0e6d78424e33c60934adc143804Ruben        return new MonthViewTouchHelper(this);
239529542558544a0e6d78424e33c60934adc143804Ruben    }
240529542558544a0e6d78424e33c60934adc143804Ruben
241e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
242e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
243e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Workaround for a JB MR1 issue where accessibility delegates on
244e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // top-level ListView items are overwritten.
245e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!mLockAccessibilityDelegate) {
246e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super.setAccessibilityDelegate(delegate);
247e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
248e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
249e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
250e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setOnDayClickListener(OnDayClickListener listener) {
251e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mOnDayClickListener = listener;
252e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
253e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
254e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
255e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean dispatchHoverEvent(MotionEvent event) {
256e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // First right-of-refusal goes the touch exploration helper.
257e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mTouchHelper.dispatchHoverEvent(event)) {
258e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return true;
259e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
260e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return super.dispatchHoverEvent(event);
261e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
262e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
263e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
264e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean onTouchEvent(MotionEvent event) {
265e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        switch (event.getAction()) {
266e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            case MotionEvent.ACTION_UP:
267e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                final int day = getDayFromLocation(event.getX(), event.getY());
268e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                if (day >= 0) {
269e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(day);
270e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                }
271e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                break;
272e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
273e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
274e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
275e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
276e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
277e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets up the text and style properties for painting. Override this if you
278e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * want to use a different paint.
279e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
280e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void initView() {
281e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint = new Paint();
282e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setFakeBoldText(true);
283e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setAntiAlias(true);
284e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
285e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
286e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setColor(mDayTextColor);
287e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextAlign(Align.CENTER);
288e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setStyle(Style.FILL);
289e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
290e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint = new Paint();
291e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setFakeBoldText(true);
292e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setAntiAlias(true);
293e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setColor(mMonthTitleBGColor);
294e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setTextAlign(Align.CENTER);
295e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setStyle(Style.FILL);
296e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
297e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint = new Paint();
298e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setFakeBoldText(true);
299e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAntiAlias(true);
300e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setColor(mTodayNumberColor);
301e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setTextAlign(Align.CENTER);
302e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setStyle(Style.FILL);
303e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
304e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
305e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint = new Paint();
306e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setAntiAlias(true);
307e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
308e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setColor(mDayTextColor);
309e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
310e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setStyle(Style.FILL);
311e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextAlign(Align.CENTER);
312e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setFakeBoldText(true);
313e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
314e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint = new Paint();
315e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setAntiAlias(true);
316e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
317e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setStyle(Style.FILL);
318e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextAlign(Align.CENTER);
319e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setFakeBoldText(false);
320e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
321e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
322e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
323e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onDraw(Canvas canvas) {
324e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthTitle(canvas);
325e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthDayLabels(canvas);
326e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthNums(canvas);
327e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
328e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
329e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int mDayOfWeekStart = 0;
330e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
331e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
332e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets all the parameters for displaying this week. The only required
333e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * parameter is the week number. Other parameters have a default value and
334e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * will only update if a new value is included, except for focus month,
335e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * which will always default to no focus month if no value is passed in. See
336e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
337e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
338e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param params A map of the new parameters, see
339e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *            {@link #VIEW_PARAMS_HEIGHT}
340e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
341e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setMonthParams(HashMap<String, Integer> params) {
342e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
343e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            throw new InvalidParameterException("You must specify month and year for this view");
344e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
345e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setTag(params);
346e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // We keep the current value for any params not present
347e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
348e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
349e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (mRowHeight < MIN_HEIGHT) {
350e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mRowHeight = MIN_HEIGHT;
351e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
352e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
353e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
354e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
355e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
356e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
357e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Allocate space for caching the day numbers and focus values
358e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonth = params.get(VIEW_PARAMS_MONTH);
359e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mYear = params.get(VIEW_PARAMS_YEAR);
360e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
361e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Figure out what day today is
362e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final Time today = new Time(Time.getCurrentTimezone());
363e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        today.setToNow();
364e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mHasToday = false;
365e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mToday = -1;
366e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
367e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.MONTH, mMonth);
368e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.YEAR, mYear);
369e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
370e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
371e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
372e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
373e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
374e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        } else {
375e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = mCalendar.getFirstDayOfWeek();
376e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
377e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
378e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumCells = Utils.getDaysInMonth(mMonth, mYear);
379e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumCells; i++) {
380e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = i + 1;
381e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (sameDay(day, today)) {
382e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mHasToday = true;
383e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mToday = day;
384e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
385e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
386e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = calculateNumRows();
387e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
388e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
389e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
390e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
391e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
3925cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public void setSelectedDay(int day) {
3935cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        mSelectedDay = day;
3945cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
3955cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
396e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void reuse() {
397e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = DEFAULT_NUM_ROWS;
398e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        requestLayout();
399e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
400e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
401e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int calculateNumRows() {
402e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int offset = findDayOffset();
403e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dividend = (offset + mNumCells) / mNumDays;
404e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int remainder = (offset + mNumCells) % mNumDays;
405e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (dividend + (remainder > 0 ? 1 : 0));
406e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
407e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
408e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private boolean sameDay(int day, Time today) {
409e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return mYear == today.year &&
410e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mMonth == today.month &&
411e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                day == today.monthDay;
412e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
413e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
414e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
415e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
416e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
417fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben                + getMonthHeaderSize());
418e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
419e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
420e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
421e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
422e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mWidth = w;
423e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
424e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
425e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
426e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
427e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
4285cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public int getMonth() {
4295cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        return mMonth;
4305cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
4315cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
4325cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public int getYear() {
4335cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        return mYear;
4345cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
4355cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
436fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben    /**
437fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben     * A wrapper to the MonthHeaderSize to allow override it in children
438fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben     */
439fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben    protected int getMonthHeaderSize() {
440fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben        return MONTH_HEADER_SIZE;
441fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben    }
442fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben
443e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String getMonthAndYearString() {
444e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
445e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                | DateUtils.FORMAT_NO_MONTH_DAY;
446e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mStringBuilder.setLength(0);
447e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        long millis = mCalendar.getTimeInMillis();
448e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags,
449e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Time.getCurrentTimezone()).toString();
450e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
451e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
4526286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected void drawMonthTitle(Canvas canvas) {
453d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein        int x = (mWidth + 2 * mEdgePadding) / 2;
454fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben        int y = (getMonthHeaderSize() - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3);
455e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
456e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
457e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
4586286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected void drawMonthDayLabels(Canvas canvas) {
459fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben        int y = getMonthHeaderSize() - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
460d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein        int dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2);
461e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
462e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumDays; i++) {
463e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int calendarDay = (i + mWeekStart) % mNumDays;
464d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein            int x = (2 * i + 1) * dayWidthHalf + mEdgePadding;
465e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
466e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
467e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
468e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mMonthDayLabelPaint);
469e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
470e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
471e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
472e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
473e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Draws the week and month day numbers for this week. Override this method
474e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * if you need different placement.
475e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
476e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas The canvas to draw on
477e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
478e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void drawMonthNums(Canvas canvas) {
479e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
480fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben                + getMonthHeaderSize();
48148bf1debfe72dee562d95f796e4fff3fe78c0bd4Ruben        final float dayWidthHalf = (mWidth - mEdgePadding * 2) / (mNumDays * 2.0f);
482e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int j = findDayOffset();
483e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
48448bf1debfe72dee562d95f796e4fff3fe78c0bd4Ruben            final int x = (int)((2 * j + 1) * dayWidthHalf + mEdgePadding);
485e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
486e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
487e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
48848bf1debfe72dee562d95f796e4fff3fe78c0bd4Ruben            final int startX = (int)(x - dayWidthHalf);
48948bf1debfe72dee562d95f796e4fff3fe78c0bd4Ruben            final int stopX = (int)(x + dayWidthHalf);
49048bf1debfe72dee562d95f796e4fff3fe78c0bd4Ruben            final int startY = (int)(y - yRelativeToDay);
49148bf1debfe72dee562d95f796e4fff3fe78c0bd4Ruben            final int stopY = (int)(startY + mRowHeight);
492e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
493e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
494e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
495e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            j++;
496e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (j == mNumDays) {
497e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                j = 0;
498e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                y += mRowHeight;
499e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
500e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
501e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
502e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
503e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
504e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This method should draw the month day.  Implemented by sub-classes to allow customization.
505e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
506e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas  The canvas to draw on
507e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param year  The year of this month day
508e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param month  The month of this month day
509e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day  The day number of this month day
510e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x  The default x position to draw the day number
511e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param y  The default y position to draw the day number
512e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startX  The left boundary of the day number rect
513e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopX  The right boundary of the day number rect
514e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startY  The top boundary of the day number rect
515e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopY  The bottom boundary of the day number rect
516e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
517e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
518e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x, int y, int startX, int stopX, int startY, int stopY);
519e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
5206286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected int findDayOffset() {
521e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
522e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                - mWeekStart;
523e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
524e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
525e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
526e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
527e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Calculates the day that the given x position is in, accounting for week
528e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * number. Returns the day or -1 if the position wasn't in a day.
529e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
530e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x The x position of the touch event
531e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The day number, or -1 if the position wasn't in a day
532e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
533e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public int getDayFromLocation(float x, float y) {
53459d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben        final int day = getInternalDayFromLocation(x, y);
53559d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben        if (day < 1 || day > mNumCells) {
53659d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben            return -1;
53759d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben        }
53859d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben        return day;
53959d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben    }
54059d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben
54159d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben    /**
54259d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben     * Calculates the day that the given x position is in, accounting for week
54359d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben     * number.
54459d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben     *
54559d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben     * @param x The x position of the touch event
54659d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben     * @return The day number
54759d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben     */
54859d94e4e4bd797430acd57c5288ec0e20bf93ba8Ruben    protected int getInternalDayFromLocation(float x, float y) {
549d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein        int dayStart = mEdgePadding;
550d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein        if (x < dayStart || x > mWidth - mEdgePadding) {
551e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return -1;
552e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
553e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
554fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben        int row = (int) (y - getMonthHeaderSize()) / mRowHeight;
555d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein        int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mEdgePadding));
556e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
557e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int day = column - findDayOffset() + 1;
558e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        day += row * mNumDays;
559e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return day;
560e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
561e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
562e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
563e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Called when the user clicks on a day. Handles callbacks to the
564e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link OnDayClickListener} if one is set.
5657c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * <p/>
5667c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * If the day is out of the range set by minDate and/or maxDate, this is a no-op.
567e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
568e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The day that was clicked
569e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
570e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void onDayClick(int day) {
5717c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        // If the min / max date are set, only process the click if it's a valid selection.
5727c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (isOutOfRange(mYear, mMonth, day)) {
5737c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return;
5747c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
5757c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5767c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
577e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mOnDayClickListener != null) {
578e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
579e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
580e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
581e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // This is a no-op if accessibility is turned off.
582e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
583e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
584e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
585e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
5867c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * @return true if the specified year/month/day are within the range set by minDate and maxDate.
5877c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * If one or either have not been set, they are considered as Integer.MIN_VALUE and
5887c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * Integer.MAX_VALUE.
5897c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     */
5907c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    protected boolean isOutOfRange(int year, int month, int day) {
5917c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (isBeforeMin(year, month, day)) {
5927c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
5937c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (isAfterMax(year, month, day)) {
5947c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
5957c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
5967c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5977c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        return false;
5987c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
5997c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6007c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    private boolean isBeforeMin(int year, int month, int day) {
601e0a0cb288106e3a25441ea57a123a812929ec79cRuben        if (mController == null) {
602e0a0cb288106e3a25441ea57a123a812929ec79cRuben            return false;
603e0a0cb288106e3a25441ea57a123a812929ec79cRuben        }
6047c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        Calendar minDate = mController.getMinDate();
6057c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (minDate == null) {
6067c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6077c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6087c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6097c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (year < minDate.get(Calendar.YEAR)) {
6107c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6117c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (year > minDate.get(Calendar.YEAR)) {
6127c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6137c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6147c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6157c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (month < minDate.get(Calendar.MONTH)) {
6167c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6177c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (month > minDate.get(Calendar.MONTH)) {
6187c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6197c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6207c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6217c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (day < minDate.get(Calendar.DAY_OF_MONTH)) {
6227c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6237c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else {
6247c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6257c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6267c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
6277c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6287c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    private boolean isAfterMax(int year, int month, int day) {
629e0a0cb288106e3a25441ea57a123a812929ec79cRuben        if (mController == null) {
630e0a0cb288106e3a25441ea57a123a812929ec79cRuben            return false;
631e0a0cb288106e3a25441ea57a123a812929ec79cRuben        }
6327c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        Calendar maxDate = mController.getMaxDate();
6337c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (maxDate == null) {
6347c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6357c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6367c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6377c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (year > maxDate.get(Calendar.YEAR)) {
6387c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6397c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (year < maxDate.get(Calendar.YEAR)) {
6407c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6417c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6427c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6437c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (month > maxDate.get(Calendar.MONTH)) {
6447c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6457c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (month < maxDate.get(Calendar.MONTH)) {
6467c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6477c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6487c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6497c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (day > maxDate.get(Calendar.DAY_OF_MONTH)) {
6507c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6517c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else {
6527c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6537c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6547c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
6557c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6567c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    /**
657e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The date that has accessibility focus, or {@code null} if no date
658e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         has focus
659e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
660e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public CalendarDay getAccessibilityFocus() {
661e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final int day = mTouchHelper.getFocusedVirtualView();
662e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (day >= 0) {
663e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return new CalendarDay(mYear, mMonth, day);
664e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
665e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return null;
666e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
667e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
668e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
669e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Clears accessibility focus within the view. No-op if the view does not
670e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * contain accessibility focus.
671e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
672e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void clearAccessibilityFocus() {
673e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.clearFocusedVirtualView();
674e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
675e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
676e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
677e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Attempts to restore accessibility focus to the specified date.
678e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
679e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The date which should receive focus
680e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return {@code false} if the date is not valid for this month view, or
681e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         {@code true} if the date received focus
682e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
683e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean restoreAccessibilityFocus(CalendarDay day) {
684e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
685e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
686e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
687e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.setFocusedVirtualView(day.day);
688e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
689e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
690e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
691e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
692e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Provides a virtual view hierarchy for interfacing with an accessibility
693e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * service.
694e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
695529542558544a0e6d78424e33c60934adc143804Ruben    protected class MonthViewTouchHelper extends ExploreByTouchHelper {
696e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private static final String DATE_FORMAT = "dd MMMM yyyy";
697e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
698e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Rect mTempRect = new Rect();
699e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Calendar mTempCalendar = Calendar.getInstance();
700e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
701e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public MonthViewTouchHelper(View host) {
702e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super(host);
703e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
704e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
705e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void setFocusedVirtualView(int virtualViewId) {
706e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getAccessibilityNodeProvider(MonthView.this).performAction(
707e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
708e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
709e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
710e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void clearFocusedVirtualView() {
711e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int focusedVirtualView = getFocusedVirtualView();
712e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
713e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                getAccessibilityNodeProvider(MonthView.this).performAction(
714e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        focusedVirtualView,
715e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
716e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        null);
717e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
718e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
719e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
720e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
721e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected int getVirtualViewAt(float x, float y) {
722e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = getDayFromLocation(x, y);
723e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day >= 0) {
724e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return day;
725e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
726e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return ExploreByTouchHelper.INVALID_ID;
727e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
728e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
729e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
730e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
731e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            for (int day = 1; day <= mNumCells; day++) {
732e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                virtualViewIds.add(day);
733e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
734e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
735e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
736e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
737e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
738e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            event.setContentDescription(getItemDescription(virtualViewId));
739e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
740e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
741e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
742e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateNodeForVirtualView(int virtualViewId,
743e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                AccessibilityNodeInfoCompat node) {
744e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getItemBounds(virtualViewId, mTempRect);
745e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
746e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setContentDescription(getItemDescription(virtualViewId));
747e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setBoundsInParent(mTempRect);
748e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
749e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
750e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (virtualViewId == mSelectedDay) {
751e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                node.setSelected(true);
752e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
753e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
754e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
755e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
756e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
757e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
758e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Bundle arguments) {
759e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            switch (action) {
760e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                case AccessibilityNodeInfo.ACTION_CLICK:
761e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(virtualViewId);
762e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    return true;
763e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
764e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
765e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
766e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
767e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
768e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
769e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Calculates the bounding rectangle of a given time object.
770e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
771e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to calculate bounds for
772e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param rect The rectangle in which to store the bounds
773e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
7746286fbddd99eaf87daf5c887b09fe51011712d80Ruben        protected void getItemBounds(int day, Rect rect) {
775d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein            final int offsetX = mEdgePadding;
776fc0e80c929547bba62f93d609c83fe6d6bfbfd34Ruben            final int offsetY = getMonthHeaderSize();
777e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int cellHeight = mRowHeight;
778d89e3fa6558283b3d9c83ca5fdded49e4c4feb14Sam Blitzstein            final int cellWidth = ((mWidth - (2 * mEdgePadding)) / mNumDays);
779e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int index = ((day - 1) + findDayOffset());
780e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int row = (index / mNumDays);
781e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int column = (index % mNumDays);
782e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int x = (offsetX + (column * cellWidth));
783e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int y = (offsetY + (row * cellHeight));
784e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
785e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            rect.set(x, y, (x + cellWidth), (y + cellHeight));
786e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
787e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
788e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
789e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Generates a description for a given time object. Since this
790e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * description will be spoken, the components are ordered by descending
791e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * specificity as DAY MONTH YEAR.
792e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
793e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to generate a description for
794e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @return A description of the time object
795e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
796529542558544a0e6d78424e33c60934adc143804Ruben        protected CharSequence getItemDescription(int day) {
797e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mTempCalendar.set(mYear, mMonth, day);
798e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final CharSequence date = DateFormat.format(DATE_FORMAT,
799e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mTempCalendar.getTimeInMillis());
800e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
801e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day == mSelectedDay) {
802e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return getContext().getString(R.string.item_is_selected, date);
803e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
804e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
805e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return date;
806e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
807e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
808e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
809e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
810e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Handles callbacks when the user clicks on a time object.
811e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
812e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public interface OnDayClickListener {
813e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void onDayClick(MonthView view, CalendarDay day);
814e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
815e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung}
816