MonthView.java revision 529542558544a0e6d78424e33c60934adc143804
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.
215529542558544a0e6d78424e33c60934adc143804Ruben        mTouchHelper = getMonthViewTouchHelper();
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
224529542558544a0e6d78424e33c60934adc143804Ruben    protected MonthViewTouchHelper getMonthViewTouchHelper() {
225529542558544a0e6d78424e33c60934adc143804Ruben        return new MonthViewTouchHelper(this);
226529542558544a0e6d78424e33c60934adc143804Ruben    }
227529542558544a0e6d78424e33c60934adc143804Ruben
228e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
229e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
230e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Workaround for a JB MR1 issue where accessibility delegates on
231e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // top-level ListView items are overwritten.
232e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!mLockAccessibilityDelegate) {
233e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super.setAccessibilityDelegate(delegate);
234e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
235e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
236e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
237e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setOnDayClickListener(OnDayClickListener listener) {
238e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mOnDayClickListener = listener;
239e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
240e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
241e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
242e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean dispatchHoverEvent(MotionEvent event) {
243e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // First right-of-refusal goes the touch exploration helper.
244e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mTouchHelper.dispatchHoverEvent(event)) {
245e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return true;
246e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
247e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return super.dispatchHoverEvent(event);
248e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
249e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
250e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
251e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean onTouchEvent(MotionEvent event) {
252e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        switch (event.getAction()) {
253e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            case MotionEvent.ACTION_UP:
254e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                final int day = getDayFromLocation(event.getX(), event.getY());
255e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                if (day >= 0) {
256e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(day);
257e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                }
258e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                break;
259e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
260e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
261e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
262e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
263e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
264e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets up the text and style properties for painting. Override this if you
265e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * want to use a different paint.
266e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
267e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void initView() {
268e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint = new Paint();
269e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setFakeBoldText(true);
270e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setAntiAlias(true);
271e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
272e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
273e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setColor(mDayTextColor);
274e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextAlign(Align.CENTER);
275e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setStyle(Style.FILL);
276e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
277e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint = new Paint();
278e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setFakeBoldText(true);
279e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setAntiAlias(true);
280e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setColor(mMonthTitleBGColor);
281e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setTextAlign(Align.CENTER);
282e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setStyle(Style.FILL);
283e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
284e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint = new Paint();
285e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setFakeBoldText(true);
286e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAntiAlias(true);
287e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setColor(mTodayNumberColor);
288e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setTextAlign(Align.CENTER);
289e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setStyle(Style.FILL);
290e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
291e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
292e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint = new Paint();
293e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setAntiAlias(true);
294e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
295e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setColor(mDayTextColor);
296e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
297e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setStyle(Style.FILL);
298e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextAlign(Align.CENTER);
299e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setFakeBoldText(true);
300e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
301e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint = new Paint();
302e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setAntiAlias(true);
303e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
304e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setStyle(Style.FILL);
305e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextAlign(Align.CENTER);
306e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setFakeBoldText(false);
307e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
308e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
309e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
310e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onDraw(Canvas canvas) {
311e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthTitle(canvas);
312e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthDayLabels(canvas);
313e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthNums(canvas);
314e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
315e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
316e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int mDayOfWeekStart = 0;
317e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
318e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
319e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets all the parameters for displaying this week. The only required
320e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * parameter is the week number. Other parameters have a default value and
321e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * will only update if a new value is included, except for focus month,
322e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * which will always default to no focus month if no value is passed in. See
323e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
324e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
325e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param params A map of the new parameters, see
326e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *            {@link #VIEW_PARAMS_HEIGHT}
327e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
328e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setMonthParams(HashMap<String, Integer> params) {
329e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
330e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            throw new InvalidParameterException("You must specify month and year for this view");
331e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
332e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setTag(params);
333e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // We keep the current value for any params not present
334e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
335e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
336e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (mRowHeight < MIN_HEIGHT) {
337e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mRowHeight = MIN_HEIGHT;
338e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
339e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
340e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
341e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
342e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
343e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
344e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Allocate space for caching the day numbers and focus values
345e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonth = params.get(VIEW_PARAMS_MONTH);
346e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mYear = params.get(VIEW_PARAMS_YEAR);
347e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
348e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Figure out what day today is
349e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final Time today = new Time(Time.getCurrentTimezone());
350e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        today.setToNow();
351e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mHasToday = false;
352e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mToday = -1;
353e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
354e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.MONTH, mMonth);
355e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.YEAR, mYear);
356e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
357e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
358e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
359e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
360e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
361e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        } else {
362e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = mCalendar.getFirstDayOfWeek();
363e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
364e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
365e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumCells = Utils.getDaysInMonth(mMonth, mYear);
366e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumCells; i++) {
367e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = i + 1;
368e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (sameDay(day, today)) {
369e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mHasToday = true;
370e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mToday = day;
371e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
372e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
373e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = calculateNumRows();
374e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
375e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
376e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
377e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
378e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
3795cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public void setSelectedDay(int day) {
3805cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        mSelectedDay = day;
3815cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
3825cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
383e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void reuse() {
384e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = DEFAULT_NUM_ROWS;
385e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        requestLayout();
386e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
387e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
388e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int calculateNumRows() {
389e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int offset = findDayOffset();
390e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dividend = (offset + mNumCells) / mNumDays;
391e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int remainder = (offset + mNumCells) % mNumDays;
392e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (dividend + (remainder > 0 ? 1 : 0));
393e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
394e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
395e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private boolean sameDay(int day, Time today) {
396e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return mYear == today.year &&
397e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mMonth == today.month &&
398e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                day == today.monthDay;
399e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
400e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
401e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
402e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
403e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
404e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                + MONTH_HEADER_SIZE);
405e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
406e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
407e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
408e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
409e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mWidth = w;
410e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
411e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
412e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
413e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
414e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
4155cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public int getMonth() {
4165cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        return mMonth;
4175cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
4185cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
4195cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public int getYear() {
4205cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        return mYear;
4215cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
4225cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
423e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String getMonthAndYearString() {
424e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
425e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                | DateUtils.FORMAT_NO_MONTH_DAY;
426e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mStringBuilder.setLength(0);
427e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        long millis = mCalendar.getTimeInMillis();
428e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags,
429e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Time.getCurrentTimezone()).toString();
430e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
431e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
432e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void drawMonthTitle(Canvas canvas) {
433e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int x = (mWidth + 2 * mPadding) / 2;
434e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3);
435e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
436e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
437e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
438e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void drawMonthDayLabels(Canvas canvas) {
439e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
440e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
441e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
442e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumDays; i++) {
443e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int calendarDay = (i + mWeekStart) % mNumDays;
444e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x = (2 * i + 1) * dayWidthHalf + mPadding;
445e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
446e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
447e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
448e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mMonthDayLabelPaint);
449e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
450e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
451e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
452e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
453e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Draws the week and month day numbers for this week. Override this method
454e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * if you need different placement.
455e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
456e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas The canvas to draw on
457e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
458e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void drawMonthNums(Canvas canvas) {
459e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
460e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                + MONTH_HEADER_SIZE;
461e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
462e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int j = findDayOffset();
463e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
464e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x = (2 * j + 1) * dayWidthHalf + mPadding;
465e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
466e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
467e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
468e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int startX = x - dayWidthHalf;
469e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int stopX = x + dayWidthHalf;
470e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int startY = y - yRelativeToDay;
471e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int stopY = startY + mRowHeight;
472e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
473e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
474e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
475e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            j++;
476e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (j == mNumDays) {
477e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                j = 0;
478e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                y += mRowHeight;
479e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
480e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
481e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
482e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
483e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
484e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This method should draw the month day.  Implemented by sub-classes to allow customization.
485e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
486e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas  The canvas to draw on
487e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param year  The year of this month day
488e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param month  The month of this month day
489e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day  The day number of this month day
490e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x  The default x position to draw the day number
491e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param y  The default y position to draw the day number
492e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startX  The left boundary of the day number rect
493e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopX  The right boundary of the day number rect
494e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startY  The top boundary of the day number rect
495e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopY  The bottom boundary of the day number rect
496e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
497e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
498e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x, int y, int startX, int stopX, int startY, int stopY);
499e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
500e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int findDayOffset() {
501e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
502e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                - mWeekStart;
503e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
504e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
505e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
506e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
507e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Calculates the day that the given x position is in, accounting for week
508e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * number. Returns the day or -1 if the position wasn't in a day.
509e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
510e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x The x position of the touch event
511e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The day number, or -1 if the position wasn't in a day
512e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
513e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public int getDayFromLocation(float x, float y) {
514e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayStart = mPadding;
515e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (x < dayStart || x > mWidth - mPadding) {
516e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return -1;
517e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
518e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
519e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int row = (int) (y - MONTH_HEADER_SIZE) / mRowHeight;
520e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
521e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
522e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int day = column - findDayOffset() + 1;
523e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        day += row * mNumDays;
524e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (day < 1 || day > mNumCells) {
525e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return -1;
526e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
527e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return day;
528e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
529e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
530e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
531e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Called when the user clicks on a day. Handles callbacks to the
532e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link OnDayClickListener} if one is set.
533e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
534e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The day that was clicked
535e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
536e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void onDayClick(int day) {
537e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mOnDayClickListener != null) {
538e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
539e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
540e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
541e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // This is a no-op if accessibility is turned off.
542e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
543e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
544e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
545e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
546e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The date that has accessibility focus, or {@code null} if no date
547e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         has focus
548e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
549e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public CalendarDay getAccessibilityFocus() {
550e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final int day = mTouchHelper.getFocusedVirtualView();
551e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (day >= 0) {
552e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return new CalendarDay(mYear, mMonth, day);
553e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
554e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return null;
555e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
556e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
557e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
558e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Clears accessibility focus within the view. No-op if the view does not
559e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * contain accessibility focus.
560e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
561e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void clearAccessibilityFocus() {
562e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.clearFocusedVirtualView();
563e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
564e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
565e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
566e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Attempts to restore accessibility focus to the specified date.
567e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
568e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The date which should receive focus
569e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return {@code false} if the date is not valid for this month view, or
570e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         {@code true} if the date received focus
571e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
572e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean restoreAccessibilityFocus(CalendarDay day) {
573e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
574e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
575e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
576e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.setFocusedVirtualView(day.day);
577e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
578e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
579e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
580e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
581e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Provides a virtual view hierarchy for interfacing with an accessibility
582e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * service.
583e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
584529542558544a0e6d78424e33c60934adc143804Ruben    protected class MonthViewTouchHelper extends ExploreByTouchHelper {
585e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private static final String DATE_FORMAT = "dd MMMM yyyy";
586e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
587e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Rect mTempRect = new Rect();
588e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Calendar mTempCalendar = Calendar.getInstance();
589e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
590e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public MonthViewTouchHelper(View host) {
591e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super(host);
592e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
593e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
594e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void setFocusedVirtualView(int virtualViewId) {
595e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getAccessibilityNodeProvider(MonthView.this).performAction(
596e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
597e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
598e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
599e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void clearFocusedVirtualView() {
600e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int focusedVirtualView = getFocusedVirtualView();
601e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
602e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                getAccessibilityNodeProvider(MonthView.this).performAction(
603e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        focusedVirtualView,
604e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
605e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        null);
606e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
607e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
608e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
609e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
610e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected int getVirtualViewAt(float x, float y) {
611e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = getDayFromLocation(x, y);
612e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day >= 0) {
613e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return day;
614e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
615e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return ExploreByTouchHelper.INVALID_ID;
616e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
617e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
618e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
619e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
620e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            for (int day = 1; day <= mNumCells; day++) {
621e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                virtualViewIds.add(day);
622e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
623e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
624e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
625e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
626e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
627e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            event.setContentDescription(getItemDescription(virtualViewId));
628e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
629e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
630e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
631e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateNodeForVirtualView(int virtualViewId,
632e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                AccessibilityNodeInfoCompat node) {
633e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getItemBounds(virtualViewId, mTempRect);
634e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
635e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setContentDescription(getItemDescription(virtualViewId));
636e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setBoundsInParent(mTempRect);
637e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
638e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
639e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (virtualViewId == mSelectedDay) {
640e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                node.setSelected(true);
641e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
642e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
643e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
644e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
645e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
646e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
647e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Bundle arguments) {
648e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            switch (action) {
649e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                case AccessibilityNodeInfo.ACTION_CLICK:
650e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(virtualViewId);
651e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    return true;
652e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
653e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
654e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
655e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
656e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
657e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
658e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Calculates the bounding rectangle of a given time object.
659e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
660e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to calculate bounds for
661e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param rect The rectangle in which to store the bounds
662e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
663e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private void getItemBounds(int day, Rect rect) {
664e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int offsetX = mPadding;
665e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int offsetY = MONTH_HEADER_SIZE;
666e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int cellHeight = mRowHeight;
667e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
668e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int index = ((day - 1) + findDayOffset());
669e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int row = (index / mNumDays);
670e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int column = (index % mNumDays);
671e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int x = (offsetX + (column * cellWidth));
672e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int y = (offsetY + (row * cellHeight));
673e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
674e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            rect.set(x, y, (x + cellWidth), (y + cellHeight));
675e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
676e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
677e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
678e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Generates a description for a given time object. Since this
679e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * description will be spoken, the components are ordered by descending
680e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * specificity as DAY MONTH YEAR.
681e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
682e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to generate a description for
683e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @return A description of the time object
684e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
685529542558544a0e6d78424e33c60934adc143804Ruben        protected CharSequence getItemDescription(int day) {
686e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mTempCalendar.set(mYear, mMonth, day);
687e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final CharSequence date = DateFormat.format(DATE_FORMAT,
688e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mTempCalendar.getTimeInMillis());
689e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
690e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day == mSelectedDay) {
691e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return getContext().getString(R.string.item_is_selected, date);
692e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
693e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
694e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return date;
695e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
696e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
697e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
698e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
699e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Handles callbacks when the user clicks on a time object.
700e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
701e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public interface OnDayClickListener {
702e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void onDayClick(MonthView view, CalendarDay day);
703e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
704e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung}
705