MonthView.java revision e0a0cb288106e3a25441ea57a123a812929ec79c
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
1237c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    protected final DatePickerController mController;
1247c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
125e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // affects the padding on the sides of this view
126e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mPadding = 0;
127e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
128e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String mDayOfWeekTypeface;
129e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String mMonthTitleTypeface;
130e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
131e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthNumPaint;
132e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthTitlePaint;
133e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthTitleBGPaint;
134e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mSelectedCirclePaint;
135e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected Paint mMonthDayLabelPaint;
136e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
137e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final Formatter mFormatter;
138e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final StringBuilder mStringBuilder;
139e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
140e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The Julian day of the first day displayed by this item
141e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mFirstJulianDay = -1;
142e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The month of the first day in this week
143e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mFirstMonth = -1;
144e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The month of the last day in this week
145e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mLastMonth = -1;
146e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
147e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonth;
148e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
149e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mYear;
150e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Quick reference to the width of this view, matches parent
151e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mWidth;
152e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The height this view should draw at in pixels, set by height param
153e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mRowHeight = DEFAULT_HEIGHT;
154e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // If this view contains the today
155e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected boolean mHasToday = false;
156e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day is selected [0-6] or -1 if no day is selected
157e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedDay = -1;
158e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day is today [0-6] or -1 if no day is today
159e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mToday = DEFAULT_SELECTED_DAY;
160e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Which day of the week to start on [0-6]
161e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mWeekStart = DEFAULT_WEEK_START;
162e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // How many days to display
163e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mNumDays = DEFAULT_NUM_DAYS;
164e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The number of days + a spot for week number if it is displayed
165e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mNumCells = mNumDays;
166e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The left edge of the selected day
167e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedLeft = -1;
168e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // The right edge of the selected day
169e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mSelectedRight = -1;
170e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
171e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final Calendar mCalendar;
1726286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected final Calendar mDayLabelCalendar;
173e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private final MonthViewTouchHelper mTouchHelper;
174e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
175e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int mNumRows = DEFAULT_NUM_ROWS;
176e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
177e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Optional listener for handling day click actions
178e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private OnDayClickListener mOnDayClickListener;
179e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    // Whether to prevent setting the accessibility delegate
180e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private boolean mLockAccessibilityDelegate;
181e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
182e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mDayTextColor;
183e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mTodayNumberColor;
1847c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    protected int mDisabledDayTextColor;
185e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonthTitleColor;
186e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected int mMonthTitleBGColor;
187e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
188e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public MonthView(Context context) {
1897c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        this(context, null);
1907c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
1917c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
1927c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    public MonthView(Context context, DatePickerController controller) {
193e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        super(context);
194e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
1957c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        mController = controller;
1967c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
197e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        Resources res = context.getResources();
198e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
199e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayLabelCalendar = Calendar.getInstance();
200e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar = Calendar.getInstance();
201e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
202e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
203e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleTypeface = res.getString(R.string.sans_serif);
204e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
205e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayTextColor = res.getColor(R.color.date_picker_text_normal);
206e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTodayNumberColor = res.getColor(R.color.blue);
2077c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        mDisabledDayTextColor = res.getColor(R.color.date_picker_text_disabled);
208e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleColor = res.getColor(R.color.white);
209e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGColor = res.getColor(R.color.circle_background);
210e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
211e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mStringBuilder = new StringBuilder(50);
212e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
213e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
214e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MINI_DAY_NUMBER_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.day_number_size);
215e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_label_size);
216e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_DAY_LABEL_TEXT_SIZE = res.getDimensionPixelSize(R.dimen.month_day_label_text_size);
217e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        MONTH_HEADER_SIZE = res.getDimensionPixelOffset(R.dimen.month_list_item_header_height);
218e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        DAY_SELECTED_CIRCLE_SIZE = res
219e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                .getDimensionPixelSize(R.dimen.day_number_select_circle_radius);
220e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
221e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mRowHeight = (res.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height)
222e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                - MONTH_HEADER_SIZE) / MAX_NUM_ROWS;
223e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
224e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Set up accessibility components.
225529542558544a0e6d78424e33c60934adc143804Ruben        mTouchHelper = getMonthViewTouchHelper();
226e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
227e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
228e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mLockAccessibilityDelegate = true;
229e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
230e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Sets up any standard paints that will be used
231e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        initView();
232e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
233e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
234529542558544a0e6d78424e33c60934adc143804Ruben    protected MonthViewTouchHelper getMonthViewTouchHelper() {
235529542558544a0e6d78424e33c60934adc143804Ruben        return new MonthViewTouchHelper(this);
236529542558544a0e6d78424e33c60934adc143804Ruben    }
237529542558544a0e6d78424e33c60934adc143804Ruben
238e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
239e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
240e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Workaround for a JB MR1 issue where accessibility delegates on
241e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // top-level ListView items are overwritten.
242e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!mLockAccessibilityDelegate) {
243e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super.setAccessibilityDelegate(delegate);
244e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
245e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
246e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
247e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setOnDayClickListener(OnDayClickListener listener) {
248e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mOnDayClickListener = listener;
249e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
250e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
251e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
252e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean dispatchHoverEvent(MotionEvent event) {
253e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // First right-of-refusal goes the touch exploration helper.
254e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mTouchHelper.dispatchHoverEvent(event)) {
255e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return true;
256e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
257e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return super.dispatchHoverEvent(event);
258e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
259e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
260e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
261e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean onTouchEvent(MotionEvent event) {
262e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        switch (event.getAction()) {
263e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            case MotionEvent.ACTION_UP:
264e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                final int day = getDayFromLocation(event.getX(), event.getY());
265e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                if (day >= 0) {
266e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(day);
267e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                }
268e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                break;
269e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
270e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
271e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
272e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
273e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
274e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets up the text and style properties for painting. Override this if you
275e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * want to use a different paint.
276e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
277e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void initView() {
278e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint = new Paint();
279e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setFakeBoldText(true);
280e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setAntiAlias(true);
281e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextSize(MONTH_LABEL_TEXT_SIZE);
282e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
283e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setColor(mDayTextColor);
284e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setTextAlign(Align.CENTER);
285e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitlePaint.setStyle(Style.FILL);
286e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
287e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint = new Paint();
288e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setFakeBoldText(true);
289e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setAntiAlias(true);
290e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setColor(mMonthTitleBGColor);
291e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setTextAlign(Align.CENTER);
292e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthTitleBGPaint.setStyle(Style.FILL);
293e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
294e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint = new Paint();
295e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setFakeBoldText(true);
296e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAntiAlias(true);
297e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setColor(mTodayNumberColor);
298e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setTextAlign(Align.CENTER);
299e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setStyle(Style.FILL);
300e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mSelectedCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
301e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
302e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint = new Paint();
303e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setAntiAlias(true);
304e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextSize(MONTH_DAY_LABEL_TEXT_SIZE);
305e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setColor(mDayTextColor);
306e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
307e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setStyle(Style.FILL);
308e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setTextAlign(Align.CENTER);
309e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthDayLabelPaint.setFakeBoldText(true);
310e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
311e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint = new Paint();
312e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setAntiAlias(true);
313e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
314e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setStyle(Style.FILL);
315e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setTextAlign(Align.CENTER);
316e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonthNumPaint.setFakeBoldText(false);
317e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
318e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
319e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
320e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onDraw(Canvas canvas) {
321e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthTitle(canvas);
322e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthDayLabels(canvas);
323e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        drawMonthNums(canvas);
324e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
325e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
326e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int mDayOfWeekStart = 0;
327e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
328e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
329e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Sets all the parameters for displaying this week. The only required
330e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * parameter is the week number. Other parameters have a default value and
331e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * will only update if a new value is included, except for focus month,
332e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * which will always default to no focus month if no value is passed in. See
333e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
334e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
335e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param params A map of the new parameters, see
336e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *            {@link #VIEW_PARAMS_HEIGHT}
337e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
338e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void setMonthParams(HashMap<String, Integer> params) {
339e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (!params.containsKey(VIEW_PARAMS_MONTH) && !params.containsKey(VIEW_PARAMS_YEAR)) {
340e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            throw new InvalidParameterException("You must specify month and year for this view");
341e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
342e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setTag(params);
343e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // We keep the current value for any params not present
344e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
345e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mRowHeight = params.get(VIEW_PARAMS_HEIGHT);
346e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (mRowHeight < MIN_HEIGHT) {
347e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mRowHeight = MIN_HEIGHT;
348e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
349e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
350e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
351e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
352e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
353e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
354e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Allocate space for caching the day numbers and focus values
355e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mMonth = params.get(VIEW_PARAMS_MONTH);
356e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mYear = params.get(VIEW_PARAMS_YEAR);
357e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
358e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Figure out what day today is
359e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final Time today = new Time(Time.getCurrentTimezone());
360e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        today.setToNow();
361e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mHasToday = false;
362e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mToday = -1;
363e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
364e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.MONTH, mMonth);
365e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.YEAR, mYear);
366e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
367e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
368e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
369e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
370e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
371e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        } else {
372e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mWeekStart = mCalendar.getFirstDayOfWeek();
373e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
374e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
375e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumCells = Utils.getDaysInMonth(mMonth, mYear);
376e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumCells; i++) {
377e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = i + 1;
378e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (sameDay(day, today)) {
379e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mHasToday = true;
380e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mToday = day;
381e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
382e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
383e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = calculateNumRows();
384e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
385e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
386e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
387e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
388e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
3895cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public void setSelectedDay(int day) {
3905cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        mSelectedDay = day;
3915cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
3925cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
393e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void reuse() {
394e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mNumRows = DEFAULT_NUM_ROWS;
395e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        requestLayout();
396e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
397e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
398e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private int calculateNumRows() {
399e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int offset = findDayOffset();
400e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dividend = (offset + mNumCells) / mNumDays;
401e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int remainder = (offset + mNumCells) % mNumDays;
402e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (dividend + (remainder > 0 ? 1 : 0));
403e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
404e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
405e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private boolean sameDay(int day, Time today) {
406e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return mYear == today.year &&
407e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                mMonth == today.month &&
408e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                day == today.monthDay;
409e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
410e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
411e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
412e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
413e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
414e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                + MONTH_HEADER_SIZE);
415e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
416e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
417e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    @Override
418e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
419e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mWidth = w;
420e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
421e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Invalidate cached accessibility information.
422e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.invalidateRoot();
423e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
424e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
4255cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public int getMonth() {
4265cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        return mMonth;
4275cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
4285cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
4295cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    public int getYear() {
4305cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein        return mYear;
4315cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein    }
4325cfe197091de6e7b5550fc2503e00fe554bef5cdSam Blitzstein
433e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private String getMonthAndYearString() {
434e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR
435e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                | DateUtils.FORMAT_NO_MONTH_DAY;
436e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mStringBuilder.setLength(0);
437e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        long millis = mCalendar.getTimeInMillis();
438e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags,
439e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Time.getCurrentTimezone()).toString();
440e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
441e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
4426286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected void drawMonthTitle(Canvas canvas) {
443e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int x = (mWidth + 2 * mPadding) / 2;
444e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = (MONTH_HEADER_SIZE - MONTH_DAY_LABEL_TEXT_SIZE) / 2 + (MONTH_LABEL_TEXT_SIZE / 3);
445e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
446e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
447e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
4486286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected void drawMonthDayLabels(Canvas canvas) {
449e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = MONTH_HEADER_SIZE - (MONTH_DAY_LABEL_TEXT_SIZE / 2);
450e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
451e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
452e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int i = 0; i < mNumDays; i++) {
453e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int calendarDay = (i + mWeekStart) % mNumDays;
454e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x = (2 * i + 1) * dayWidthHalf + mPadding;
455e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
456e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            canvas.drawText(mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT,
457e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    Locale.getDefault()).toUpperCase(Locale.getDefault()), x, y,
458e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mMonthDayLabelPaint);
459e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
460e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
461e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
462e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
463e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Draws the week and month day numbers for this week. Override this method
464e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * if you need different placement.
465e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
466e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas The canvas to draw on
467e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
468e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    protected void drawMonthNums(Canvas canvas) {
469e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int y = (((mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH)
470e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                + MONTH_HEADER_SIZE;
471e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
472e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int j = findDayOffset();
473e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        for (int dayNumber = 1; dayNumber <= mNumCells; dayNumber++) {
474e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x = (2 * j + 1) * dayWidthHalf + mPadding;
475e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
476e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int yRelativeToDay = (mRowHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2 - DAY_SEPARATOR_WIDTH;
477e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
478e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int startX = x - dayWidthHalf;
479e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int stopX = x + dayWidthHalf;
480e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int startY = y - yRelativeToDay;
481e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int stopY = startY + mRowHeight;
482e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
483e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            drawMonthDay(canvas, mYear, mMonth, dayNumber, x, y, startX, stopX, startY, stopY);
484e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
485e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            j++;
486e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (j == mNumDays) {
487e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                j = 0;
488e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                y += mRowHeight;
489e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
490e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
491e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
492e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
493e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
494e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * This method should draw the month day.  Implemented by sub-classes to allow customization.
495e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
496e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param canvas  The canvas to draw on
497e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param year  The year of this month day
498e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param month  The month of this month day
499e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day  The day number of this month day
500e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x  The default x position to draw the day number
501e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param y  The default y position to draw the day number
502e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startX  The left boundary of the day number rect
503e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopX  The right boundary of the day number rect
504e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param startY  The top boundary of the day number rect
505e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param stopY  The bottom boundary of the day number rect
506e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
507e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public abstract void drawMonthDay(Canvas canvas, int year, int month, int day,
508e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            int x, int y, int startX, int stopX, int startY, int stopY);
509e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
5106286fbddd99eaf87daf5c887b09fe51011712d80Ruben    protected int findDayOffset() {
511e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart)
512e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                - mWeekStart;
513e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
514e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
515e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
516e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
517e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Calculates the day that the given x position is in, accounting for week
518e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * number. Returns the day or -1 if the position wasn't in a day.
519e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
520e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param x The x position of the touch event
521e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The day number, or -1 if the position wasn't in a day
522e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
523e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public int getDayFromLocation(float x, float y) {
524e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int dayStart = mPadding;
525e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (x < dayStart || x > mWidth - mPadding) {
526e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return -1;
527e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
528e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
529e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int row = (int) (y - MONTH_HEADER_SIZE) / mRowHeight;
530e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
531e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
532e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        int day = column - findDayOffset() + 1;
533e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        day += row * mNumDays;
534e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (day < 1 || day > mNumCells) {
535e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return -1;
536e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
537e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return day;
538e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
539e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
540e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
541e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Called when the user clicks on a day. Handles callbacks to the
542e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * {@link OnDayClickListener} if one is set.
5437c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * <p/>
5447c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * If the day is out of the range set by minDate and/or maxDate, this is a no-op.
545e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
546e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The day that was clicked
547e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
548e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    private void onDayClick(int day) {
5497c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        // If the min / max date are set, only process the click if it's a valid selection.
5507c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (isOutOfRange(mYear, mMonth, day)) {
5517c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return;
5527c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
5537c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5547c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
555e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (mOnDayClickListener != null) {
556e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mOnDayClickListener.onDayClick(this, new CalendarDay(mYear, mMonth, day));
557e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
558e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
559e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        // This is a no-op if accessibility is turned off.
560e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
561e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
562e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
563e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
5647c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * @return true if the specified year/month/day are within the range set by minDate and maxDate.
5657c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * If one or either have not been set, they are considered as Integer.MIN_VALUE and
5667c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     * Integer.MAX_VALUE.
5677c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein     */
5687c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    protected boolean isOutOfRange(int year, int month, int day) {
5697c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (isBeforeMin(year, month, day)) {
5707c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
5717c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (isAfterMax(year, month, day)) {
5727c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
5737c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
5747c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5757c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        return false;
5767c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
5777c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5787c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    private boolean isBeforeMin(int year, int month, int day) {
579e0a0cb288106e3a25441ea57a123a812929ec79cRuben        if (mController == null) {
580e0a0cb288106e3a25441ea57a123a812929ec79cRuben            return false;
581e0a0cb288106e3a25441ea57a123a812929ec79cRuben        }
5827c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        Calendar minDate = mController.getMinDate();
5837c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (minDate == null) {
5847c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
5857c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
5867c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5877c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (year < minDate.get(Calendar.YEAR)) {
5887c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
5897c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (year > minDate.get(Calendar.YEAR)) {
5907c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
5917c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
5927c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5937c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (month < minDate.get(Calendar.MONTH)) {
5947c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
5957c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (month > minDate.get(Calendar.MONTH)) {
5967c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
5977c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
5987c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
5997c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (day < minDate.get(Calendar.DAY_OF_MONTH)) {
6007c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6017c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else {
6027c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6037c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6047c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
6057c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6067c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    private boolean isAfterMax(int year, int month, int day) {
607e0a0cb288106e3a25441ea57a123a812929ec79cRuben        if (mController == null) {
608e0a0cb288106e3a25441ea57a123a812929ec79cRuben            return false;
609e0a0cb288106e3a25441ea57a123a812929ec79cRuben        }
6107c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        Calendar maxDate = mController.getMaxDate();
6117c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (maxDate == null) {
6127c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6137c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6147c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6157c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (year > maxDate.get(Calendar.YEAR)) {
6167c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6177c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (year < maxDate.get(Calendar.YEAR)) {
6187c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6197c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6207c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6217c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (month > maxDate.get(Calendar.MONTH)) {
6227c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6237c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else if (month < maxDate.get(Calendar.MONTH)) {
6247c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6257c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6267c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6277c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        if (day > maxDate.get(Calendar.DAY_OF_MONTH)) {
6287c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return true;
6297c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        } else {
6307c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein            return false;
6317c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein        }
6327c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    }
6337c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein
6347c597aee285f2fe4a13a24334493c6eadddd7939Sam Blitzstein    /**
635e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return The date that has accessibility focus, or {@code null} if no date
636e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         has focus
637e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
638e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public CalendarDay getAccessibilityFocus() {
639e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        final int day = mTouchHelper.getFocusedVirtualView();
640e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if (day >= 0) {
641e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return new CalendarDay(mYear, mMonth, day);
642e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
643e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return null;
644e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
645e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
646e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
647e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Clears accessibility focus within the view. No-op if the view does not
648e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * contain accessibility focus.
649e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
650e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public void clearAccessibilityFocus() {
651e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.clearFocusedVirtualView();
652e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
653e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
654e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
655e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Attempts to restore accessibility focus to the specified date.
656e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *
657e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @param day The date which should receive focus
658e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * @return {@code false} if the date is not valid for this month view, or
659e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     *         {@code true} if the date received focus
660e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
661e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public boolean restoreAccessibilityFocus(CalendarDay day) {
662e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        if ((day.year != mYear) || (day.month != mMonth) || (day.day > mNumCells)) {
663e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
664e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
665e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        mTouchHelper.setFocusedVirtualView(day.day);
666e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        return true;
667e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
668e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
669e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
670e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Provides a virtual view hierarchy for interfacing with an accessibility
671e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * service.
672e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
673529542558544a0e6d78424e33c60934adc143804Ruben    protected class MonthViewTouchHelper extends ExploreByTouchHelper {
674e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private static final String DATE_FORMAT = "dd MMMM yyyy";
675e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
676e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Rect mTempRect = new Rect();
677e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        private final Calendar mTempCalendar = Calendar.getInstance();
678e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
679e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public MonthViewTouchHelper(View host) {
680e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            super(host);
681e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
682e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
683e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void setFocusedVirtualView(int virtualViewId) {
684e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getAccessibilityNodeProvider(MonthView.this).performAction(
685e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    virtualViewId, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
686e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
687e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
688e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void clearFocusedVirtualView() {
689e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int focusedVirtualView = getFocusedVirtualView();
690e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
691e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                getAccessibilityNodeProvider(MonthView.this).performAction(
692e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        focusedVirtualView,
693e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
694e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                        null);
695e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
696e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
697e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
698e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
699e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected int getVirtualViewAt(float x, float y) {
700e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int day = getDayFromLocation(x, y);
701e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day >= 0) {
702e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return day;
703e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
704e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return ExploreByTouchHelper.INVALID_ID;
705e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
706e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
707e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
708e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
709e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            for (int day = 1; day <= mNumCells; day++) {
710e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                virtualViewIds.add(day);
711e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
712e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
713e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
714e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
715e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
716e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            event.setContentDescription(getItemDescription(virtualViewId));
717e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
718e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
719e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
720e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected void onPopulateNodeForVirtualView(int virtualViewId,
721e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                AccessibilityNodeInfoCompat node) {
722e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            getItemBounds(virtualViewId, mTempRect);
723e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
724e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setContentDescription(getItemDescription(virtualViewId));
725e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.setBoundsInParent(mTempRect);
726e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
727e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
728e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (virtualViewId == mSelectedDay) {
729e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                node.setSelected(true);
730e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
731e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
732e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
733e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
734e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        @Override
735e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
736e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                Bundle arguments) {
737e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            switch (action) {
738e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                case AccessibilityNodeInfo.ACTION_CLICK:
739e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    onDayClick(virtualViewId);
740e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    return true;
741e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
742e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
743e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return false;
744e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
745e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
746e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
747e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Calculates the bounding rectangle of a given time object.
748e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
749e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to calculate bounds for
750e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param rect The rectangle in which to store the bounds
751e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
7526286fbddd99eaf87daf5c887b09fe51011712d80Ruben        protected void getItemBounds(int day, Rect rect) {
753e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int offsetX = mPadding;
754e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int offsetY = MONTH_HEADER_SIZE;
755e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int cellHeight = mRowHeight;
756e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
757e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int index = ((day - 1) + findDayOffset());
758e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int row = (index / mNumDays);
759e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int column = (index % mNumDays);
760e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int x = (offsetX + (column * cellWidth));
761e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final int y = (offsetY + (row * cellHeight));
762e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
763e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            rect.set(x, y, (x + cellWidth), (y + cellHeight));
764e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
765e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
766e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        /**
767e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * Generates a description for a given time object. Since this
768e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * description will be spoken, the components are ordered by descending
769e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * specificity as DAY MONTH YEAR.
770e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         *
771e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @param day The day to generate a description for
772e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         * @return A description of the time object
773e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung         */
774529542558544a0e6d78424e33c60934adc143804Ruben        protected CharSequence getItemDescription(int day) {
775e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            mTempCalendar.set(mYear, mMonth, day);
776e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            final CharSequence date = DateFormat.format(DATE_FORMAT,
777e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                    mTempCalendar.getTimeInMillis());
778e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
779e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            if (day == mSelectedDay) {
780e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung                return getContext().getString(R.string.item_is_selected, date);
781e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            }
782e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
783e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung            return date;
784e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        }
785e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
786e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung
787e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    /**
788e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     * Handles callbacks when the user clicks on a time object.
789e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung     */
790e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    public interface OnDayClickListener {
791e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung        public void onDayClick(MonthView view, CalendarDay day);
792e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung    }
793e668d6b1b77ac4b127f961150e0d0a8a088143d9James Kung}
794