MonthView.java revision e668d6b1b77ac4b127f961150e0d0a8a088143d9
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;
34e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.MotionEvent;
35e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.View;
36e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.accessibility.AccessibilityEvent;
37e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport android.view.accessibility.AccessibilityNodeInfo;
38e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
39e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport com.android.datetimepicker.R;
40e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport com.android.datetimepicker.Utils;
41e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport com.android.datetimepicker.date.MonthAdapter.CalendarDay;
42e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
43e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.security.InvalidParameterException;
44e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.Calendar;
45e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.Formatter;
46e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.HashMap;
47e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.List;
48e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungimport java.util.Locale;
49e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
50e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung/**
51e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * A calendar-like view displaying a specified month and the appropriate selectable day numbers
52e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung * within the specified month.
53e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung */
54e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kungpublic abstract class MonthView extends View {
55e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private static final String TAG = "MonthView";
56e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
57e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
58e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * These params can be passed into the view to control how it appears.
59e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
60e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * values are unlikely to fit most layouts correctly.
61e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
62e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
63e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This sets the height of this week in pixels
64e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
65e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_HEIGHT = "height";
66e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
67e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This specifies the position (or weeks since the epoch) of this week,
68e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
69e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
70e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_MONTH = "month";
71e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
72e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This specifies the position (or weeks since the epoch) of this week,
73e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
74e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
75e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_YEAR = "year";
76e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
77e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This sets one of the days in this view as selected {@link Time#SUNDAY}
78e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * through {@link Time#SATURDAY}.
79e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
80e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
81e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
82e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Which day the week should start on. {@link Time#SUNDAY} through
83e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link Time#SATURDAY}.
84e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
85e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_WEEK_START = "week_start";
86e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
87e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * How many days to display at a time. Days will be displayed starting with
88e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #mWeekStart}.
89e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
90e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
91e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
92e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Which month is currently in focus, as defined by {@link Time#month}
93e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * [0-11].
94e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
95e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
96e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
97e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * If this month should display week numbers. false if 0, true otherwise.
98e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
99e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
100e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
101e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int DEFAULT_HEIGHT = 32;
102e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MIN_HEIGHT = 10;
103e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_SELECTED_DAY = -1;
104e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
105e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_NUM_DAYS = 7;
106e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_SHOW_WK_NUM = 0;
107e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_FOCUS_MONTH = -1;
108e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int DEFAULT_NUM_ROWS = 6;
109e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static final int MAX_NUM_ROWS = 6;
110e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
111e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private static final int SELECTED_CIRCLE_ALPHA = 60;
112e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
113e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int DAY_SEPARATOR_WIDTH = 1;
114e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MINI_DAY_NUMBER_TEXT_SIZE;
115e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MONTH_LABEL_TEXT_SIZE;
116e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MONTH_DAY_LABEL_TEXT_SIZE;
117e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int MONTH_HEADER_SIZE;
118e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static int DAY_SELECTED_CIRCLE_SIZE;
119e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
120e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // used for scaling to the device density
121e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected static float mScale = 0;
122e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
123e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // affects the padding on the sides of this view
124e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mPadding = 0;
125e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
126e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String mDayOfWeekTypeface;
127e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String mMonthTitleTypeface;
128e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
129e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthNumPaint;
130e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthTitlePaint;
131e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthTitleBGPaint;
132e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mSelectedCirclePaint;
133e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthDayLabelPaint;
134e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
135e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final Formatter mFormatter;
136e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final StringBuilder mStringBuilder;
137e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
138e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The Julian day of the first day displayed by this item
139e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mFirstJulianDay = -1;
140e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The month of the first day in this week
141e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mFirstMonth = -1;
142e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The month of the last day in this week
143e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mLastMonth = -1;
144e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
145e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonth;
146e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
147e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mYear;
148e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Quick reference to the width of this view, matches parent
149e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mWidth;
150e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The height this view should draw at in pixels, set by height param
151e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mRowHeight = DEFAULT_HEIGHT;
152e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // If this view contains the today
153e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected boolean mHasToday = false;
154e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day is selected [0-6] or -1 if no day is selected
155e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedDay = -1;
156e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day is today [0-6] or -1 if no day is today
157e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mToday = DEFAULT_SELECTED_DAY;
158e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day of the week to start on [0-6]
159e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mWeekStart = DEFAULT_WEEK_START;
160e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // How many days to display
161e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mNumDays = DEFAULT_NUM_DAYS;
162e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The number of days + a spot for week number if it is displayed
163e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mNumCells = mNumDays;
164e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The left edge of the selected day
165e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedLeft = -1;
166e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The right edge of the selected day
167e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedRight = -1;
168e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
169e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final Calendar mCalendar;
170e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final Calendar mDayLabelCalendar;
171e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final MonthViewTouchHelper mTouchHelper;
172e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
173e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int mNumRows = DEFAULT_NUM_ROWS;
174e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
175e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Optional listener for handling day click actions
176e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private OnDayClickListener mOnDayClickListener;
177e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Whether to prevent setting the accessibility delegate
178e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private boolean mLockAccessibilityDelegate;
179e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
180e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mDayTextColor;
181e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mTodayNumberColor;
182e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonthTitleColor;
183e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonthTitleBGColor;
184e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
185e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public MonthView(Context context) {
186e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        super(context);
187e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
188e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        Resources res = context.getResources();
189e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
190e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayLabelCalendar = Calendar.getInstance();
191e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar = Calendar.getInstance();
192e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
193e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
194e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleTypeface = res.getString(R.string.sans_serif);
195e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
196e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayTextColor = res.getColor(R.color.date_picker_text_normal);
197e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTodayNumberColor = res.getColor(R.color.blue);
198e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleColor = res.getColor(R.color.white);
199e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGColor = res.getColor(R.color.circle_background);
200e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
201e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mStringBuilder = new StringBuilder(50);
202e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
203e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
204e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.day_number_size);
205e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_label_size);
206e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_day_label_text_size);
207e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.month_list_item_header_height);
208e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        DAY_SELECTED_CIRCLE_SIZE = res
209e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                .getDimensionPixelSize(R.dimen.day_number_select_circle_radius);
210e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
211e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mRowHeight = (res.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height)
212e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                - MONTH_HEADER_SIZE) / MAX_NUM_ROWS;
213e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
214e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Set up accessibility components.
215e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper = new MonthViewTouchHelper(this);
216e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
217e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
218e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mLockAccessibilityDelegate = true;
219e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
220e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Sets up any standard paints that will be used
221e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        initView();
222e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
223e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
224e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
225e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
226e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Workaround for a JB MR1 issue where accessibility delegates on
227e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // top-level ListView items are overwritten.
228e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!mLockAccessibilityDelegate) {
229e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super.setAccessibilityDelegate(delegate);
230e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
231e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
232e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
233e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setOnDayClickListener(OnDayClickListener listener) {
234e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mOnDayClickListener = listener;
235e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
236e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
237e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
238e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean dispatchHoverEvent(MotionEvent event) {
239e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // First right-of-refusal goes the touch exploration helper.
240e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mTouchHelper.dispatchHoverEvent(event)) {
241e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return true;
242e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
243e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return super.dispatchHoverEvent(event);
244e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
245e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
246e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
247e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean onTouchEvent(MotionEvent event) {
248e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        switch (event.getAction()) {
249e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            case MotionEvent.ACTION_UP:
250e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                final int day = getDayFromLocation(event.getX(), event.getY());
251e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                if (day >= 0) {
252e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(day);
253e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                }
254e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                break;
255e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
256e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
257e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
258e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
259e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
260e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets up the text and style properties for painting. Override this if you
261e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * want to use a different paint.
262e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
263e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void initView() {
264e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint = new Paint();
265e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setFakeBoldText(true);
266e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setAntiAlias(true);
267e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
268e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
269e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setColor(mDayTextColor);
270e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextAlign(Align.CENTER);
271e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setStyle(Style.FILL);
272e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
273e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint = new Paint();
274e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setFakeBoldText(true);
275e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setAntiAlias(true);
276e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setColor(mMonthTitleBGColor);
277e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setTextAlign(Align.CENTER);
278e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setStyle(Style.FILL);
279e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
280e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint = new Paint();
281e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setFakeBoldText(true);
282e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAntiAlias(true);
283e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setColor(mTodayNumberColor);
284e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setTextAlign(Align.CENTER);
285e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setStyle(Style.FILL);
286e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
287e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
288e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint = new Paint();
289e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setAntiAlias(true);
290e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
291e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setColor(mDayTextColor);
292e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
293e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setStyle(Style.FILL);
294e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextAlign(Align.CENTER);
295e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setFakeBoldText(true);
296e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
297e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint = new Paint();
298e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setAntiAlias(true);
299e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
300e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setStyle(Style.FILL);
301e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextAlign(Align.CENTER);
302e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setFakeBoldText(false);
303e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
304e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
305e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
306e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onDraw(Canvas canvas) {
307e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthTitle(canvas);
308e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthDayLabels(canvas);
309e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthNums(canvas);
310e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
311e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
312e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int mDayOfWeekStart = 0;
313e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
314e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
315e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets all the parameters for displaying this week. The only required
316e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * parameter is the week number. Other parameters have a default value and
317e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * will only update if a new value is included, except for focus month,
318e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * which will always default to no focus month if no value is passed in. See
319e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
320e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
321e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param params A map of the new parameters, see
322e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *            {@link #VIEW_PARAMS_HEIGHT}
323e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
324e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setMonthParams(HashMap<String, Integer> params) {
325e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
326e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            throw new InvalidParameterException("You must specify month and year for this view");
327e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
328e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setTag(params);
329e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // We keep the current value for any params not present
330e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
331e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
332e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (mRowHeight < MIN_HEIGHT) {
333e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mRowHeight = MIN_HEIGHT;
334e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
335e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
336e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
337e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
338e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
339e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
340e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Allocate space for caching the day numbers and focus values
341e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonth = params.get(VIEW_PARAMS_MONTH);
342e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mYear = params.get(VIEW_PARAMS_YEAR);
343e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
344e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Figure out what day today is
345e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final Time today = new Time(Time.getCurrentTimezone());
346e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        today.setToNow();
347e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mHasToday = false;
348e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mToday = -1;
349e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
350e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.MONTH, mMonth);
351e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.YEAR, mYear);
352e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
353e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
354e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
355e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
356e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
357e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        } else {
358e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = mCalendar.getFirstDayOfWeek();
359e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
360e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
361e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumCells = Utils.getDaysInMonth(mMonth, mYear);
362e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumCells; i++) {
363e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = i + 1;
364e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (sameDay(day, today)) {
365e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mHasToday = true;
366e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mToday = day;
367e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
368e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
369e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = calculateNumRows();
370e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
371e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
372e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
373e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
374e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
375e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void reuse() {
376e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = DEFAULT_NUM_ROWS;
377e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        requestLayout();
378e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
379e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
380e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int calculateNumRows() {
381e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int offset = findDayOffset();
382e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dividend = (offset + mNumCells) / mNumDays;
383e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int remainder = (offset + mNumCells) % mNumDays;
384e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (dividend + (remainder > 0 ? 1 : 0));
385e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
386e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
387e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private boolean sameDay(int day, Time today) {
388e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return mYear == today.year &&
389e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mMonth == today.month &&
390e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                day == today.monthDay;
391e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
392e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
393e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
394e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
395e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
396e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                + MONTH_HEADER_SIZE);
397e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
398e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
399e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
400e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
401e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mWidth = w;
402e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
403e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
404e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
405e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
406e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
407e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String getMonthAndYearString() {
408e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
409e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                | DateUtils.FORMAT_NO_MONTH_DAY;
410e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mStringBuilder.setLength(0);
411e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        long millis = mCalendar.getTimeInMillis();
412e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags,
413e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Time.getCurrentTimezone()).toString();
414e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
415e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
416e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void drawMonthTitle(Canvas canvas) {
417e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int x = (mWidth + 2 * mPadding) / 2;
418e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3);
419e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
420e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
421e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
422e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void drawMonthDayLabels(Canvas canvas) {
423e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
424e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
425e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
426e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumDays; i++) {
427e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int calendarDay = (i + mWeekStart) % mNumDays;
428e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x = (2 * i + 1) * dayWidthHalf + mPadding;
429e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
430e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
431e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
432e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mMonthDayLabelPaint);
433e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
434e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
435e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
436e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
437e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Draws the week and month day numbers for this week. Override this method
438e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * if you need different placement.
439e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
440e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas The canvas to draw on
441e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
442e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void drawMonthNums(Canvas canvas) {
443e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
444e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                + MONTH_HEADER_SIZE;
445e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
446e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int j = findDayOffset();
447e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
448e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x = (2 * j + 1) * dayWidthHalf + mPadding;
449e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
450e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
451e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
452e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int startX = x - dayWidthHalf;
453e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int stopX = x + dayWidthHalf;
454e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int startY = y - yRelativeToDay;
455e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int stopY = startY + mRowHeight;
456e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
457e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
458e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
459e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            j++;
460e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (j == mNumDays) {
461e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                j = 0;
462e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                y += mRowHeight;
463e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
464e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
465e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
466e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
467e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
468e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This method should draw the month day.  Implemented by sub-classes to allow customization.
469e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
470e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas  The canvas to draw on
471e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param year  The year of this month day
472e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param month  The month of this month day
473e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day  The day number of this month day
474e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x  The default x position to draw the day number
475e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param y  The default y position to draw the day number
476e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startX  The left boundary of the day number rect
477e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopX  The right boundary of the day number rect
478e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startY  The top boundary of the day number rect
479e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopY  The bottom boundary of the day number rect
480e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
481e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
482e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x, int y, int startX, int stopX, int startY, int stopY);
483e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
484e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int findDayOffset() {
485e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
486e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                - mWeekStart;
487e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
488e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
489e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
490e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
491e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Calculates the day that the given x position is in, accounting for week
492e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * number. Returns the day or -1 if the position wasn't in a day.
493e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
494e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x The x position of the touch event
495e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The day number, or -1 if the position wasn't in a day
496e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
497e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public int getDayFromLocation(float x, float y) {
498e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayStart = mPadding;
499e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (x < dayStart || x > mWidth - mPadding) {
500e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return -1;
501e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
502e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
503e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int row = (int) (y - MONTH_HEADER_SIZE) / mRowHeight;
504e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
505e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
506e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int day = column - findDayOffset() + 1;
507e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        day += row * mNumDays;
508e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (day < 1 || day > mNumCells) {
509e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return -1;
510e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
511e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return day;
512e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
513e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
514e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
515e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Called when the user clicks on a day. Handles callbacks to the
516e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link OnDayClickListener} if one is set.
517e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
518e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The day that was clicked
519e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
520e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void onDayClick(int day) {
521e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mOnDayClickListener != null) {
522e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
523e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
524e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
525e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // This is a no-op if accessibility is turned off.
526e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
527e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
528e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
529e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
530e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The date that has accessibility focus, or {@code null} if no date
531e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         has focus
532e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
533e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public CalendarDay getAccessibilityFocus() {
534e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final int day = mTouchHelper.getFocusedVirtualView();
535e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (day >= 0) {
536e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return new CalendarDay(mYear, mMonth, day);
537e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
538e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return null;
539e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
540e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
541e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
542e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Clears accessibility focus within the view. No-op if the view does not
543e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * contain accessibility focus.
544e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
545e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void clearAccessibilityFocus() {
546e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.clearFocusedVirtualView();
547e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
548e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
549e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
550e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Attempts to restore accessibility focus to the specified date.
551e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
552e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The date which should receive focus
553e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return {@code false} if the date is not valid for this month view, or
554e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         {@code true} if the date received focus
555e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
556e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean restoreAccessibilityFocus(CalendarDay day) {
557e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
558e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
559e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
560e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.setFocusedVirtualView(day.day);
561e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
562e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
563e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
564e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
565e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Provides a virtual view hierarchy for interfacing with an accessibility
566e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * service.
567e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
568e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private class MonthViewTouchHelper extends ExploreByTouchHelper {
569e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private static final String DATE_FORMAT = "dd MMMM yyyy";
570e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
571e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Rect mTempRect = new Rect();
572e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Calendar mTempCalendar = Calendar.getInstance();
573e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
574e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public MonthViewTouchHelper(View host) {
575e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super(host);
576e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
577e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
578e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void setFocusedVirtualView(int virtualViewId) {
579e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getAccessibilityNodeProvider(MonthView.this).performAction(
580e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
581e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
582e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
583e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void clearFocusedVirtualView() {
584e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int focusedVirtualView = getFocusedVirtualView();
585e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
586e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                getAccessibilityNodeProvider(MonthView.this).performAction(
587e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        focusedVirtualView,
588e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
589e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        null);
590e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
591e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
592e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
593e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
594e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected int getVirtualViewAt(float x, float y) {
595e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = getDayFromLocation(x, y);
596e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day >= 0) {
597e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return day;
598e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
599e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return ExploreByTouchHelper.INVALID_ID;
600e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
601e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
602e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
603e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
604e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            for (int day = 1; day <= mNumCells; day++) {
605e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                virtualViewIds.add(day);
606e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
607e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
608e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
609e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
610e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
611e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            event.setContentDescription(getItemDescription(virtualViewId));
612e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
613e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
614e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
615e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateNodeForVirtualView(int virtualViewId,
616e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                AccessibilityNodeInfoCompat node) {
617e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getItemBounds(virtualViewId, mTempRect);
618e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
619e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setContentDescription(getItemDescription(virtualViewId));
620e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setBoundsInParent(mTempRect);
621e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
622e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
623e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (virtualViewId == mSelectedDay) {
624e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                node.setSelected(true);
625e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
626e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
627e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
628e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
629e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
630e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
631e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Bundle arguments) {
632e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            switch (action) {
633e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                case AccessibilityNodeInfo.ACTION_CLICK:
634e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(virtualViewId);
635e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    return true;
636e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
637e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
638e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
639e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
640e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
641e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
642e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Calculates the bounding rectangle of a given time object.
643e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
644e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to calculate bounds for
645e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param rect The rectangle in which to store the bounds
646e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
647e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private void getItemBounds(int day, Rect rect) {
648e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int offsetX = mPadding;
649e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int offsetY = MONTH_HEADER_SIZE;
650e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int cellHeight = mRowHeight;
651e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
652e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int index = ((day - 1) + findDayOffset());
653e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int row = (index / mNumDays);
654e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int column = (index % mNumDays);
655e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int x = (offsetX + (column * cellWidth));
656e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int y = (offsetY + (row * cellHeight));
657e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
658e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            rect.set(x, y, (x + cellWidth), (y + cellHeight));
659e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
660e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
661e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
662e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Generates a description for a given time object. Since this
663e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * description will be spoken, the components are ordered by descending
664e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * specificity as DAY MONTH YEAR.
665e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
666e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to generate a description for
667e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @return A description of the time object
668e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
669e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private CharSequence getItemDescription(int day) {
670e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mTempCalendar.set(mYear, mMonth, day);
671e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final CharSequence date = DateFormat.format(DATE_FORMAT,
672e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mTempCalendar.getTimeInMillis());
673e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
674e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day == mSelectedDay) {
675e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return getContext().getString(R.string.item_is_selected, date);
676e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
677e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
678e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return date;
679e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
680e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
681e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
682e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
683e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Handles callbacks when the user clicks on a time object.
684e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
685e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public interface OnDayClickListener {
686e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void onDayClick(MonthView view, CalendarDay day);
687e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
688e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung}
689