SimpleMonthView.java revision d5c85c824bfe343144af68ede9e86f12d5bba1f7
1bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio/*
2bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Copyright (C) 2014 The Android Open Source Project
3bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *
4bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Licensed under the Apache License, Version 2.0 (the "License");
5bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * you may not use this file except in compliance with the License.
6bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * You may obtain a copy of the License at
7bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *
8bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *      http://www.apache.org/licenses/LICENSE-2.0
9bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio *
10bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * Unless required by applicable law or agreed to in writing, software
11bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * distributed under the License is distributed on an "AS IS" BASIS,
12bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * See the License for the specific language governing permissions and
14bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * limitations under the License.
15bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */
16bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
17bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Megliopackage android.widget;
18bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
19d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viveretteimport com.android.internal.R;
20d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viveretteimport com.android.internal.widget.ExploreByTouchHelper;
21d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette
22e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.annotation.Nullable;
23bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.Context;
24bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.ColorStateList;
25bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.Resources;
260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.content.res.TypedArray;
27bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Canvas;
28bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint;
29bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Align;
30bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Paint.Style;
31bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Rect;
32bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.graphics.Typeface;
33a7f827bab3df423d2a3b527853de3e9ac9e378fdDeepanshu Guptaimport android.icu.text.SimpleDateFormat;
34bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.os.Bundle;
355dc973cb03889c58988703b58aefbd2397fb02c3Alan Viveretteimport android.text.TextPaint;
36bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.text.format.DateFormat;
37bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.util.AttributeSet;
38ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viveretteimport android.util.IntArray;
39da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viveretteimport android.util.Log;
405c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viveretteimport android.util.MathUtils;
41c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viveretteimport android.util.StateSet;
42e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.view.KeyEvent;
43bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.MotionEvent;
44bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.View;
45e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mountimport android.view.ViewParent;
46bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityEvent;
47bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viveretteimport android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
49bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
501ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haaseimport java.text.NumberFormat;
51e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viveretteimport java.util.Arrays;
52bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Calendar;
53bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Locale;
54bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
55d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viveretteimport libcore.icu.LocaleData;
56d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette
57bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio/**
58bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * A calendar-like view displaying a specified month and the appropriate selectable day numbers
59bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * within the specified month.
60bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */
61bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioclass SimpleMonthView extends View {
62e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private static final String LOG_TAG = "SimpleMonthView";
63e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private static final int DAYS_IN_WEEK = 7;
650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private static final int MAX_WEEKS_IN_MONTH = 6;
66bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
67bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static final int DEFAULT_SELECTED_DAY = -1;
68bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
69bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
70e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private static final String MONTH_YEAR_FORMAT = "MMMMy";
71c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
72e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private static final int SELECTED_HIGHLIGHT_ALPHA = 0xB0;
73e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
74da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette    /** Temporary until we figure out why the date gets messed up. */
75da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette    private static final boolean DEBUG_WRONG_DATE = true;
76da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette
775dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette    private final TextPaint mMonthPaint = new TextPaint();
785dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette    private final TextPaint mDayOfWeekPaint = new TextPaint();
795dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette    private final TextPaint mDayPaint = new TextPaint();
800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final Paint mDaySelectorPaint = new Paint();
810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final Paint mDayHighlightPaint = new Paint();
82e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private final Paint mDayHighlightSelectorPaint = new Paint();
835dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette
84d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette    /** Array of single-character weekday labels ordered by column index. */
85e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private final String[] mDayOfWeekLabels = new String[7];
86e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
87e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private final Calendar mCalendar;
88e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private final Locale mLocale;
89bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private final MonthViewTouchHelper mTouchHelper;
91fd2dd208d9b0957657589b26c14c48b8e2b7f387Alan Viverette
921ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase    private final NumberFormat mDayFormatter;
93bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
94f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    // Desired dimensions.
95f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredMonthHeight;
96f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredDayOfWeekHeight;
97f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredDayHeight;
98f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredCellWidth;
99f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private final int mDesiredDaySelectorRadius;
10060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
101e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private String mMonthYearLabel;
102bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
103bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mMonth;
104bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mYear;
105bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
106f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    // Dimensions as laid out.
107f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mMonthHeight;
108f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mDayOfWeekHeight;
109f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mDayHeight;
110f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mCellWidth;
111f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    private int mDaySelectorRadius;
112f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
1130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mPaddedWidth;
1140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mPaddedHeight;
115bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The day of month for the selected day, or -1 if no day is selected. */
117c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    private int mActivatedDay = -1;
118bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
1200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * The day of month for today, or -1 if the today is not in the current
1210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * month.
1220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
123bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mToday = DEFAULT_SELECTED_DAY;
124bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
125d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette    /** The first day of the week (ex. Calendar.SUNDAY) indexed from one. */
126bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mWeekStart = DEFAULT_WEEK_START;
127bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The number of days (ex. 28) in the current month. */
1290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mDaysInMonth;
130bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
1320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * The day of week (ex. Calendar.SUNDAY) for the first day of the current
1330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * month.
1340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
1350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int mDayOfWeekStart;
136bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The day of month for the first (inclusive) enabled day. */
138bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mEnabledDayStart = 1;
139bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** The day of month for the last (inclusive) enabled day. */
141bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mEnabledDayEnd = 31;
142bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1430ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /** Optional listener for handling day click actions. */
144bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private OnDayClickListener mOnDayClickListener;
145bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
146c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    private ColorStateList mDayTextColor;
147c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
148e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private int mHighlightedDay = -1;
149e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private int mPreviouslyHighlightedDay = -1;
150e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private boolean mIsTouchHighlighted = false;
15160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
152bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public SimpleMonthView(Context context) {
153bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        this(context, null);
154bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
155bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
156bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public SimpleMonthView(Context context, AttributeSet attrs) {
157bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        this(context, attrs, R.attr.datePickerStyle);
158bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
159bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
16050eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) {
16150eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        this(context, attrs, defStyleAttr, 0);
16250eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    }
163bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
16450eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
16550eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        super(context, attrs, defStyleAttr, defStyleRes);
166bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
16750eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        final Resources res = context.getResources();
168f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height);
169f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height);
170f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height);
171f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
17278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        mDesiredDaySelectorRadius = res.getDimensionPixelSize(
17378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette                R.dimen.date_picker_day_selector_radius);
17460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
175bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Set up accessibility components.
176bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper = new MonthViewTouchHelper(this);
177bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setAccessibilityDelegate(mTouchHelper);
178bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
179bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
180e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        mLocale = res.getConfiguration().locale;
181e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        mCalendar = Calendar.getInstance(mLocale);
182e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
183e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        mDayFormatter = NumberFormat.getIntegerInstance(mLocale);
184e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
185e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        updateMonthYearLabel();
186e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        updateDayOfWeekLabels();
1870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
1880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        initPaints(res);
1890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
1900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
191e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private void updateMonthYearLabel() {
192e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        final String format = DateFormat.getBestDateTimePattern(mLocale, MONTH_YEAR_FORMAT);
193e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        final SimpleDateFormat formatter = new SimpleDateFormat(format, mLocale);
194e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        mMonthYearLabel = formatter.format(mCalendar.getTime());
195e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    }
196e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
197e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    private void updateDayOfWeekLabels() {
198da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        if (DEBUG_WRONG_DATE) {
199da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette            Log.d(LOG_TAG, "enter updateDayOfWeekLabels()", new Exception());
200da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette            Log.d(LOG_TAG, "mLocale => " + mLocale);
201da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette            Log.d(LOG_TAG, "mWeekStart => " + mWeekStart);
202da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        }
203da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette
204d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        // Use tiny (e.g. single-character) weekday names from ICU. The indices
205d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        // for this list correspond to Calendar days, e.g. SUNDAY is index 1.
206d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        final String[] tinyWeekdayNames = LocaleData.get(mLocale).tinyWeekdayNames;
207d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        for (int i = 0; i < DAYS_IN_WEEK; i++) {
208d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            mDayOfWeekLabels[i] = tinyWeekdayNames[(mWeekStart + i - 1) % DAYS_IN_WEEK + 1];
209e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        }
210da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette
211da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        if (DEBUG_WRONG_DATE) {
212da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette            Log.d(LOG_TAG, "mDayOfWeekLabels <= " + Arrays.toString(mDayOfWeekLabels));
213da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        }
214e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    }
215e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
2160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
2170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Applies the specified text appearance resource to a paint, returning the
2180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * text color if one is set in the text appearance.
2190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
2200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param p the paint to modify
2210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param resId the resource ID of the text appearance
2220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @return the text color, if available
2230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
2240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private ColorStateList applyTextAppearance(Paint p, int resId) {
2250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final TypedArray ta = mContext.obtainStyledAttributes(null,
2260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.styleable.TextAppearance, 0, resId);
2270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (fontFamily != null) {
2300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            p.setTypeface(Typeface.create(fontFamily, 0));
2310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
2320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        p.setTextSize(ta.getDimensionPixelSize(
2340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.styleable.TextAppearance_textSize, (int) p.getTextSize()));
2350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (textColor != null) {
2380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final int enabledColor = textColor.getColorForState(ENABLED_STATE_SET, 0);
2390ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            p.setColor(enabledColor);
2400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
2410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        ta.recycle();
2430ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return textColor;
2450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
24778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette    public int getMonthHeight() {
24878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        return mMonthHeight;
24978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette    }
25078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette
25178bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette    public int getCellWidth() {
25278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        return mCellWidth;
25378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette    }
25478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette
2550ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setMonthTextAppearance(int resId) {
25678bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        applyTextAppearance(mMonthPaint, resId);
25760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
2580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
2590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setDayOfWeekTextAppearance(int resId) {
2620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        applyTextAppearance(mDayOfWeekPaint, resId);
2630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
2640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setDayTextAppearance(int resId) {
2670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final ColorStateList textColor = applyTextAppearance(mDayPaint, resId);
2680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (textColor != null) {
2690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            mDayTextColor = textColor;
2700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
2710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
2730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
2740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
275c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    /**
276c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette     * Sets up the text and style properties for painting.
277c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette     */
2780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void initPaints(Resources res) {
2790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String monthTypeface = res.getString(R.string.date_picker_month_typeface);
2800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String dayOfWeekTypeface = res.getString(R.string.date_picker_day_of_week_typeface);
2810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final String dayTypeface = res.getString(R.string.date_picker_day_typeface);
2820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
2830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int monthTextSize = res.getDimensionPixelSize(
2840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.dimen.date_picker_month_text_size);
2850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int dayOfWeekTextSize = res.getDimensionPixelSize(
2860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.dimen.date_picker_day_of_week_text_size);
2870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int dayTextSize = res.getDimensionPixelSize(
2880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                R.dimen.date_picker_day_text_size);
2890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
290c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setAntiAlias(true);
2910ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mMonthPaint.setTextSize(monthTextSize);
2920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mMonthPaint.setTypeface(Typeface.create(monthTypeface, 0));
293c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setTextAlign(Align.CENTER);
294c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setStyle(Style.FILL);
295c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
296c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setAntiAlias(true);
2970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayOfWeekPaint.setTextSize(dayOfWeekTextSize);
2980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayOfWeekPaint.setTypeface(Typeface.create(dayOfWeekTypeface, 0));
299c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setTextAlign(Align.CENTER);
300c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setStyle(Style.FILL);
301c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
3020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaySelectorPaint.setAntiAlias(true);
3030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaySelectorPaint.setStyle(Style.FILL);
3040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
3050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayHighlightPaint.setAntiAlias(true);
3060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayHighlightPaint.setStyle(Style.FILL);
307c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
308e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        mDayHighlightSelectorPaint.setAntiAlias(true);
309e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        mDayHighlightSelectorPaint.setStyle(Style.FILL);
310e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
311c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayPaint.setAntiAlias(true);
3120ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayPaint.setTextSize(dayTextSize);
3130ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayPaint.setTypeface(Typeface.create(dayTypeface, 0));
314c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayPaint.setTextAlign(Align.CENTER);
315c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayPaint.setStyle(Style.FILL);
316bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
317bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
318c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    void setMonthTextColor(ColorStateList monthTextColor) {
319c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0);
320c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mMonthPaint.setColor(enabledColor);
321c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
322c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    }
323bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
324c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) {
325c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0);
326c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayOfWeekPaint.setColor(enabledColor);
327c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
328c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    }
329bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
330c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    void setDayTextColor(ColorStateList dayTextColor) {
331c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mDayTextColor = dayTextColor;
332c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
333c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette    }
334bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
3350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    void setDaySelectorColor(ColorStateList dayBackgroundColor) {
3365dc973cb03889c58988703b58aefbd2397fb02c3Alan Viverette        final int activatedColor = dayBackgroundColor.getColorForState(
337c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
3380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaySelectorPaint.setColor(activatedColor);
339e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        mDayHighlightSelectorPaint.setColor(activatedColor);
340e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        mDayHighlightSelectorPaint.setAlpha(SELECTED_HIGHLIGHT_ALPHA);
341c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        invalidate();
342bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
343bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
3440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    void setDayHighlightColor(ColorStateList dayHighlightColor) {
3450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int pressedColor = dayHighlightColor.getColorForState(
3460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED), 0);
3470ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDayHighlightPaint.setColor(pressedColor);
3480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
349bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
350bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
351bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public void setOnDayClickListener(OnDayClickListener listener) {
352bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mOnDayClickListener = listener;
353bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
354bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
355bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
356bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public boolean dispatchHoverEvent(MotionEvent event) {
357bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // First right-of-refusal goes the touch exploration helper.
3580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return mTouchHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
359bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
360bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
361bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
362bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public boolean onTouchEvent(MotionEvent event) {
36360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int x = (int) (event.getX() + 0.5f);
36460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int y = (int) (event.getY() + 0.5f);
36560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
366ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        final int action = event.getAction();
367ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        switch (action) {
3680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_DOWN:
3690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_MOVE:
37078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette                final int touchedItem = getDayAtLocation(x, y);
371e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                mIsTouchHighlighted = true;
372e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (mHighlightedDay != touchedItem) {
373e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    mHighlightedDay = touchedItem;
374e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    mPreviouslyHighlightedDay = touchedItem;
3750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                    invalidate();
376bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
377ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                if (action == MotionEvent.ACTION_DOWN && touchedItem < 0) {
378ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                    // Touch something that's not an item, reject event.
379ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                    return false;
380ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                }
381bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                break;
3820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
3830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_UP:
38478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette                final int clickedDay = getDayAtLocation(x, y);
38578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette                onDayClicked(clickedDay);
3860ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // Fall through.
3870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            case MotionEvent.ACTION_CANCEL:
3880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // Reset touched day on stream end.
389e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                mHighlightedDay = -1;
390e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                mIsTouchHighlighted = false;
3910ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                invalidate();
3920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                break;
393bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
394bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return true;
395bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
396bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
397bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
398e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    public boolean onKeyDown(int keyCode, KeyEvent event) {
399e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        // We need to handle focus change within the SimpleMonthView because we are simulating
400e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        // multiple Views. The arrow keys will move between days until there is no space (no
401e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        // day to the left, top, right, or bottom). Focus forward and back jumps out of the
402e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        // SimpleMonthView, skipping over other SimpleMonthViews in the parent ViewPager
403e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        // to the next focusable View in the hierarchy.
404e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        boolean focusChanged = false;
405e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        switch (event.getKeyCode()) {
406e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            case KeyEvent.KEYCODE_DPAD_LEFT:
407e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (event.hasNoModifiers()) {
408e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    focusChanged = moveOneDay(isLayoutRtl());
409e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
410e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                break;
411e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            case KeyEvent.KEYCODE_DPAD_RIGHT:
412e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (event.hasNoModifiers()) {
413e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    focusChanged = moveOneDay(!isLayoutRtl());
414e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
415e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                break;
416e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            case KeyEvent.KEYCODE_DPAD_UP:
417e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (event.hasNoModifiers()) {
418e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    ensureFocusedDay();
419e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    if (mHighlightedDay > 7) {
420e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        mHighlightedDay -= 7;
421e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        focusChanged = true;
422e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    }
423e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
424e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                break;
425e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            case KeyEvent.KEYCODE_DPAD_DOWN:
426e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (event.hasNoModifiers()) {
427e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    ensureFocusedDay();
428e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    if (mHighlightedDay <= mDaysInMonth - 7) {
429e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        mHighlightedDay += 7;
430e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        focusChanged = true;
431e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    }
432e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
433e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                break;
434e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            case KeyEvent.KEYCODE_DPAD_CENTER:
435e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            case KeyEvent.KEYCODE_ENTER:
436e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (mHighlightedDay != -1) {
437e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    onDayClicked(mHighlightedDay);
438e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    return true;
439e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
440e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                break;
441e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            case KeyEvent.KEYCODE_TAB: {
442e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                int focusChangeDirection = 0;
443e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (event.hasNoModifiers()) {
444e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    focusChangeDirection = View.FOCUS_FORWARD;
445e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
446e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    focusChangeDirection = View.FOCUS_BACKWARD;
447e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
448e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                if (focusChangeDirection != 0) {
449e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    final ViewParent parent = getParent();
450e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    // move out of the ViewPager next/previous
451e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    View nextFocus = this;
452e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    do {
453e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        nextFocus = nextFocus.focusSearch(focusChangeDirection);
454e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    } while (nextFocus != null && nextFocus != this &&
455e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                            nextFocus.getParent() == parent);
456e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    if (nextFocus != null) {
457e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        nextFocus.requestFocus();
458e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        return true;
459e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    }
460e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
461e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                break;
462e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            }
463e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
464e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (focusChanged) {
465e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            invalidate();
466e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return true;
467e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        } else {
468e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return super.onKeyDown(keyCode, event);
469e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
470e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
471e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
472e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private boolean moveOneDay(boolean positive) {
473e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        ensureFocusedDay();
474e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        boolean focusChanged = false;
475e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (positive) {
476e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            if (!isLastDayOfWeek(mHighlightedDay) && mHighlightedDay < mDaysInMonth) {
477e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                mHighlightedDay++;
478e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                focusChanged = true;
479e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            }
480e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        } else {
481e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            if (!isFirstDayOfWeek(mHighlightedDay) && mHighlightedDay > 1) {
482e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                mHighlightedDay--;
483e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                focusChanged = true;
484e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            }
485e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
486e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        return focusChanged;
487e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
488e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
489e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    @Override
490e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
491e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            @Nullable Rect previouslyFocusedRect) {
492e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (gainFocus) {
493e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            // If we've gained focus through arrow keys, we should find the day closest
494e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            // to the focus rect. If we've gained focus through forward/back, we should
495e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            // focus on the selected day if there is one.
496e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final int offset = findDayOffset();
497e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            switch(direction) {
498e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                case View.FOCUS_RIGHT: {
499e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    int row = findClosestRow(previouslyFocusedRect);
500e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    mHighlightedDay = row == 0 ? 1 : (row * DAYS_IN_WEEK) - offset + 1;
501e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    break;
502e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
503e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                case View.FOCUS_LEFT: {
504e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    int row = findClosestRow(previouslyFocusedRect) + 1;
505e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    mHighlightedDay = Math.min(mDaysInMonth, (row * DAYS_IN_WEEK) - offset);
506e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    break;
507e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
508e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                case View.FOCUS_DOWN: {
509e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    final int col = findClosestColumn(previouslyFocusedRect);
510e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    final int day = col - offset + 1;
511e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    mHighlightedDay = day < 1 ? day + DAYS_IN_WEEK : day;
512e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    break;
513e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
514e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                case View.FOCUS_UP: {
515e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    final int col = findClosestColumn(previouslyFocusedRect);
516e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    final int maxWeeks = (offset + mDaysInMonth) / DAYS_IN_WEEK;
517e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    final int day = col - offset + (DAYS_IN_WEEK * maxWeeks) + 1;
518e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    mHighlightedDay = day > mDaysInMonth ? day - DAYS_IN_WEEK : day;
519e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    break;
520e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                }
521e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            }
522e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            ensureFocusedDay();
523e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            invalidate();
524e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
525e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
526e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
527e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
528e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    /**
529e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     * Returns the row (0 indexed) closest to previouslyFocusedRect or center if null.
530e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     */
531e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private int findClosestRow(@Nullable Rect previouslyFocusedRect) {
532e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (previouslyFocusedRect == null) {
533e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return 3;
534e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        } else {
535e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            int centerY = previouslyFocusedRect.centerY();
536e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
537e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final TextPaint p = mDayPaint;
538e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final int headerHeight = mMonthHeight + mDayOfWeekHeight;
539e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final int rowHeight = mDayHeight;
540e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
541e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            // Text is vertically centered within the row height.
542e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
543e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final int rowCenter = headerHeight + rowHeight / 2;
544e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
545e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            centerY -= rowCenter - halfLineHeight;
546e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            int row = Math.round(centerY / (float) rowHeight);
547e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final int maxDay = findDayOffset() + mDaysInMonth;
548e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final int maxRows = (maxDay / DAYS_IN_WEEK) - ((maxDay % DAYS_IN_WEEK == 0) ? 1 : 0);
549e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
550e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            row = MathUtils.constrain(row, 0, maxRows);
551e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return row;
552e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
553e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
554e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
555e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    /**
556e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     * Returns the column (0 indexed) closest to the previouslyFocusedRect or center if null.
557e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     * The 0 index is related to the first day of the week.
558e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     */
559e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private int findClosestColumn(@Nullable Rect previouslyFocusedRect) {
560e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (previouslyFocusedRect == null) {
561e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return DAYS_IN_WEEK / 2;
562e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        } else {
563e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            int centerX = previouslyFocusedRect.centerX() - mPaddingLeft;
564e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final int columnFromLeft =
565e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                    MathUtils.constrain(centerX / mCellWidth, 0, DAYS_IN_WEEK - 1);
566e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return isLayoutRtl() ? DAYS_IN_WEEK - columnFromLeft - 1: columnFromLeft;
567e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
568e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
569e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
570e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    @Override
571e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    public void getFocusedRect(Rect r) {
572e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (mHighlightedDay > 0) {
573e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            getBoundsForDay(mHighlightedDay, r);
574e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        } else {
575e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            super.getFocusedRect(r);
576e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
577e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
578e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
579e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    @Override
580e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    protected void onFocusLost() {
581e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (!mIsTouchHighlighted) {
582e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            // Unhighlight a day.
583e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            mPreviouslyHighlightedDay = mHighlightedDay;
584e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            mHighlightedDay = -1;
585e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            invalidate();
586e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
587e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        super.onFocusLost();
588e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
589e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
590e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    /**
591e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     * Ensure some day is highlighted. If a day isn't highlighted, it chooses the selected day,
592e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     * if possible, or the first day of the month if not.
593e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount     */
594e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private void ensureFocusedDay() {
595e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (mHighlightedDay != -1) {
596e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return;
597e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
598e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (mPreviouslyHighlightedDay != -1) {
599e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            mHighlightedDay = mPreviouslyHighlightedDay;
600e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return;
601e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
602e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        if (mActivatedDay != -1) {
603e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            mHighlightedDay = mActivatedDay;
604e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            return;
605e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        }
606e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        mHighlightedDay = 1;
607e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
608e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
609e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private boolean isFirstDayOfWeek(int day) {
610e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        final int offset = findDayOffset();
611e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        return (offset + day - 1) % DAYS_IN_WEEK == 0;
612e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
613e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
614e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    private boolean isLastDayOfWeek(int day) {
615e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        final int offset = findDayOffset();
616e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount        return (offset + day) % DAYS_IN_WEEK == 0;
617e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    }
618e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount
619e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount    @Override
620bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected void onDraw(Canvas canvas) {
6210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int paddingLeft = getPaddingLeft();
6220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int paddingTop = getPaddingTop();
6230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        canvas.translate(paddingLeft, paddingTop);
6240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        drawMonth(canvas);
6260ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        drawDaysOfWeek(canvas);
627bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        drawDays(canvas);
6280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        canvas.translate(-paddingLeft, -paddingTop);
6300ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
6310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void drawMonth(Canvas canvas) {
6330ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final float x = mPaddedWidth / 2f;
6340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Vertically centered within the month header height.
6360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent();
6370ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final float y = (mMonthHeight - lineHeight) / 2f;
6380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
639e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        canvas.drawText(mMonthYearLabel, x, y, mMonthPaint);
640e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    }
641e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
642e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette    public String getMonthYearLabel() {
643e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        return mMonthYearLabel;
6440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
6450ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6460ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void drawDaysOfWeek(Canvas canvas) {
64760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final TextPaint p = mDayOfWeekPaint;
64860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int headerHeight = mMonthHeight;
64960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowHeight = mDayOfWeekHeight;
650f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int colWidth = mCellWidth;
65160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
65260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Text is vertically centered within the day of week height.
65360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
65460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowCenter = headerHeight + rowHeight / 2;
65560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
65660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        for (int col = 0; col < DAYS_IN_WEEK; col++) {
65760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int colCenter = colWidth * col + colWidth / 2;
658ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            final int colCenterRtl;
659ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            if (isLayoutRtl()) {
660ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                colCenterRtl = mPaddedWidth - colCenter;
661ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            } else {
662ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                colCenterRtl = colCenter;
663ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            }
664ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette
665d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            final String label = mDayOfWeekLabels[col];
666ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            canvas.drawText(label, colCenterRtl, rowCenter - halfLineHeight, p);
6670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
6680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
6690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
6710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Draws the month days.
6720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
6730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private void drawDays(Canvas canvas) {
67460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final TextPaint p = mDayPaint;
67560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
67660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowHeight = mDayHeight;
677f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int colWidth = mCellWidth;
6780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
67960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Text is vertically centered within the row height.
68060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
68160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        int rowCenter = headerHeight + rowHeight / 2;
6820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
68360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
68460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final int colCenter = colWidth * col + colWidth / 2;
685ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            final int colCenterRtl;
686ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            if (isLayoutRtl()) {
687ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                colCenterRtl = mPaddedWidth - colCenter;
688ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            } else {
689ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette                colCenterRtl = colCenter;
690ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            }
691ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette
6920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            int stateMask = 0;
6930ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6945c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            final boolean isDayEnabled = isDayEnabled(day);
6955c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            if (isDayEnabled) {
6960ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                stateMask |= StateSet.VIEW_STATE_ENABLED;
6970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
6980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
6990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final boolean isDayActivated = mActivatedDay == day;
700e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            final boolean isDayHighlighted = mHighlightedDay == day;
7010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            if (isDayActivated) {
7020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                stateMask |= StateSet.VIEW_STATE_ACTIVATED;
7030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
7040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // Adjust the circle to be centered on the row.
705e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                final Paint paint = isDayHighlighted ? mDayHighlightSelectorPaint :
706e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                        mDaySelectorPaint;
707e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount                canvas.drawCircle(colCenterRtl, rowCenter, mDaySelectorRadius, paint);
708e998c3f5f6e7ac21b18cd449088ac2b76b65671fGeorge Mount            } else if (isDayHighlighted) {
7090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                stateMask |= StateSet.VIEW_STATE_PRESSED;
7100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
7115c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette                if (isDayEnabled) {
7125c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette                    // Adjust the circle to be centered on the row.
7135c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette                    canvas.drawCircle(colCenterRtl, rowCenter,
7145c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette                            mDaySelectorRadius, mDayHighlightPaint);
7155c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette                }
7160ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
7170ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
7180ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final boolean isDayToday = mToday == day;
7190ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            final int dayTextColor;
7200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            if (isDayToday && !isDayActivated) {
7210ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                dayTextColor = mDaySelectorPaint.getColor();
7220ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            } else {
7230ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                final int[] stateSet = StateSet.get(stateMask);
7240ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
7250ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
72660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            p.setColor(dayTextColor);
7270ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
7281ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase            canvas.drawText(mDayFormatter.format(day), colCenterRtl, rowCenter - halfLineHeight, p);
7290ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
73060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            col++;
7310ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
73260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            if (col == DAYS_IN_WEEK) {
73360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                col = 0;
73460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                rowCenter += rowHeight;
7350ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
7360ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
737bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
738bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
7395c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette    private boolean isDayEnabled(int day) {
7405c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette        return day >= mEnabledDayStart && day <= mEnabledDayEnd;
7415c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette    }
7425c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette
7435c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette    private boolean isValidDayOfMonth(int day) {
7445c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette        return day >= 1 && day <= mDaysInMonth;
7455c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette    }
7465c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette
747518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette    private static boolean isValidDayOfWeek(int day) {
7485878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette        return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY;
7495878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette    }
7505878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette
7515878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette    private static boolean isValidMonth(int month) {
7525878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette        return month >= Calendar.JANUARY && month <= Calendar.DECEMBER;
75375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio    }
75475b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio
755bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
7560ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Sets the selected day.
757bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
7580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param dayOfMonth the selected day of the month, or {@code -1} to clear
7590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *                   the selection
7600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
7610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setSelectedDay(int dayOfMonth) {
7620ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mActivatedDay = dayOfMonth;
7630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
7640ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Invalidate cached accessibility information.
7650ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mTouchHelper.invalidateRoot();
7660ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
7670ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
7680ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
7690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
7700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Sets the first day of the week.
7710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
7720ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param weekStart which day the week should start on, valid values are
7730ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *                  {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
7740ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     */
7750ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    public void setFirstDayOfWeek(int weekStart) {
776da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        if (DEBUG_WRONG_DATE) {
777da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette            Log.d(LOG_TAG, "enter setFirstDayOfWeek(" + weekStart + ")", new Exception());
778da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        }
779da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette
7800ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (isValidDayOfWeek(weekStart)) {
7810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            mWeekStart = weekStart;
7820ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        } else {
7830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            mWeekStart = mCalendar.getFirstDayOfWeek();
7840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
7850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
786da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        if (DEBUG_WRONG_DATE) {
787da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette            Log.d(LOG_TAG, "mWeekStart <=" + mWeekStart);
788da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette        }
789da7cc2d2ae2cf8742d12773852d8de6cd16201d7Alan Viverette
790e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        updateDayOfWeekLabels();
791e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
7920ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Invalidate cached accessibility information.
7930ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mTouchHelper.invalidateRoot();
7940ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        invalidate();
7950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    }
7960ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
7970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    /**
7980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Sets all the parameters for displaying this week.
7990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * <p>
8000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Parameters have a default value and will only update if a new value is
8010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * included, except for focus month, which will always default to no focus
8020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * month if no value is passed in. The only required parameter is the week
8030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * start.
8040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
8050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param selectedDay the selected day of the month, or -1 for no selection
8060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param month the month
8070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param year the year
8080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param weekStart which day the week should start on, valid values are
8090ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *                  {@link Calendar#SUNDAY} through {@link Calendar#SATURDAY}
8100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param enabledDayStart the first enabled day
8110ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param enabledDayEnd the last enabled day
812bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
81375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio    void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
81475b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            int enabledDayEnd) {
815d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        if (DEBUG_WRONG_DATE) {
816d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            Log.d(LOG_TAG, "setMonthParams(" + selectedDay + ", " + month + ", " + year + ", "
817d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette                    + weekStart + ", " + enabledDayStart + ", " + enabledDayEnd + ")");
818d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        }
819d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette
820c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette        mActivatedDay = selectedDay;
821bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
8225878076456a2eb510c03b6160a0cfb99a54e0cfdAlan Viverette        if (isValidMonth(month)) {
82375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            mMonth = month;
82475b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio        }
82575b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio        mYear = year;
826bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
827bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mCalendar.set(Calendar.MONTH, mMonth);
828bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mCalendar.set(Calendar.YEAR, mYear);
829bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mCalendar.set(Calendar.DAY_OF_MONTH, 1);
830bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK);
831bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
832518ff0de95e64116ecb07706fc564d4c19197ca7Alan Viverette        if (isValidDayOfWeek(weekStart)) {
83375b121553f9c7498581a4b10d932f1c499feef25Fabrice Di Meglio            mWeekStart = weekStart;
834bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        } else {
835bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mWeekStart = mCalendar.getFirstDayOfWeek();
836bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
837bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
8380ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        // Figure out what day today is.
8390ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final Calendar today = Calendar.getInstance();
8400ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mToday = -1;
8410ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        mDaysInMonth = getDaysInMonth(mMonth, mYear);
8420ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        for (int i = 0; i < mDaysInMonth; i++) {
843bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            final int day = i + 1;
844bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (sameDay(day, today)) {
845bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                mToday = day;
846bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
847bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
8480ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
8495c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette        mEnabledDayStart = MathUtils.constrain(enabledDayStart, 1, mDaysInMonth);
8505c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette        mEnabledDayEnd = MathUtils.constrain(enabledDayEnd, mEnabledDayStart, mDaysInMonth);
8515c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette
852bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Invalidate cached accessibility information.
853bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper.invalidateRoot();
854e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette
855e264f9503b8ffcb2cae84e4e5bb2a03dd0610221Alan Viverette        updateMonthYearLabel();
856d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette
857d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        if (DEBUG_WRONG_DATE) {
858d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            Log.d(LOG_TAG, "mMonth = " + mMonth);
859d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            Log.d(LOG_TAG, "mDayOfWeekStart = " + mDayOfWeekStart);
860d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            Log.d(LOG_TAG, "mWeekStart = " + mWeekStart);
861d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            Log.d(LOG_TAG, "mDaysInMonth = " + mDaysInMonth);
862d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette            Log.d(LOG_TAG, "mToday = " + mToday);
863d5c85c824bfe343144af68ede9e86f12d5bba1f7Alan Viverette        }
864bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
865bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
866bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static int getDaysInMonth(int month, int year) {
867bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        switch (month) {
868bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.JANUARY:
869bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.MARCH:
870bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.MAY:
871bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.JULY:
872bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.AUGUST:
873bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.OCTOBER:
874bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.DECEMBER:
875bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return 31;
876bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.APRIL:
877bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.JUNE:
878bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.SEPTEMBER:
879bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.NOVEMBER:
880bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return 30;
881bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            case Calendar.FEBRUARY:
882bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return (year % 4 == 0) ? 29 : 28;
883bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            default:
884bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                throw new IllegalArgumentException("Invalid Month");
885bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
886bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
887bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
8880ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private boolean sameDay(int day, Calendar today) {
8890ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return mYear == today.get(Calendar.YEAR) && mMonth == today.get(Calendar.MONTH)
8900ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                && day == today.get(Calendar.DAY_OF_MONTH);
891bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
892bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
893bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
894bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
895816aa142023c75cf427401c21831193998bdf233Alan Viverette        final int preferredHeight = mDesiredDayHeight * MAX_WEEKS_IN_MONTH
896816aa142023c75cf427401c21831193998bdf233Alan Viverette                + mDesiredDayOfWeekHeight + mDesiredMonthHeight
897816aa142023c75cf427401c21831193998bdf233Alan Viverette                + getPaddingTop() + getPaddingBottom();
898f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK
8990ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                + getPaddingStart() + getPaddingEnd();
9000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec);
9010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec);
9020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        setMeasuredDimension(resolvedWidth, resolvedHeight);
903bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
904bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
905bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
906ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette    public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
907ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        super.onRtlPropertiesChanged(layoutDirection);
908ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette
909ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        requestLayout();
910ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette    }
911ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette
912ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette    @Override
913f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
914f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        if (!changed) {
915f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette            return;
916f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        }
917f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
918f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // Let's initialize a completely reasonable number of variables.
919f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int w = right - left;
920f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int h = bottom - top;
921f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingLeft = getPaddingLeft();
922f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingTop = getPaddingTop();
923f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingRight = getPaddingRight();
924f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddingBottom = getPaddingBottom();
925f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedRight = w - paddingRight;
926f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedBottom = h - paddingBottom;
927f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedWidth = paddedRight - paddingLeft;
928f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int paddedHeight = paddedBottom - paddingTop;
929f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) {
930f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette            return;
931f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        }
932f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
933f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mPaddedWidth = paddedWidth;
934f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mPaddedHeight = paddedHeight;
935f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
936f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // We may have been laid out smaller than our preferred size. If so,
937f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // scale all dimensions to fit.
938f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom;
939f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final float scaleH = paddedHeight / (float) measuredPaddedHeight;
940f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int monthHeight = (int) (mDesiredMonthHeight * scaleH);
94160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
942f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mMonthHeight = monthHeight;
943f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH);
944f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDayHeight = (int) (mDesiredDayHeight * scaleH);
945f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mCellWidth = cellWidth;
946f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette
947f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // Compute the largest day selector radius that's still within the clip
948f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        // bounds and desired selector radius.
949f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight);
950f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int maxSelectorHeight = mDayHeight / 2 + paddingBottom;
951f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius,
952f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette                Math.min(maxSelectorWidth, maxSelectorHeight));
95360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
954bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Invalidate cached accessibility information.
955bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper.invalidateRoot();
956bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
957bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
9580ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette    private int findDayOffset() {
9590ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int offset = mDayOfWeekStart - mWeekStart;
9600ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (mDayOfWeekStart < mWeekStart) {
9610ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return offset + DAYS_IN_WEEK;
962bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
9630ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return offset;
964bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
965bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
966bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
96778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette     * Calculates the day of the month at the specified touch position. Returns
96878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette     * the day of the month or -1 if the position wasn't in a valid day.
9690ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     *
9700ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param x the x position of the touch event
9710ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param y the y position of the touch event
97278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette     * @return the day of the month at (x, y), or -1 if the position wasn't in
97378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette     *         a valid day
974bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
97578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette    private int getDayAtLocation(int x, int y) {
97660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int paddedX = x - getPaddingLeft();
9770ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (paddedX < 0 || paddedX >= mPaddedWidth) {
9780ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return -1;
9790ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
980c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
9810ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
98260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int paddedY = y - getPaddingTop();
9830ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        if (paddedY < headerHeight || paddedY >= mPaddedHeight) {
9840ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return -1;
9850ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        }
986c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette
987ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        // Adjust for RTL after applying padding.
988ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        final int paddedXRtl;
989ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        if (isLayoutRtl()) {
990ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            paddedXRtl = mPaddedWidth - paddedX;
991ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        } else {
992ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            paddedXRtl = paddedX;
993ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        }
994ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette
9950ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int row = (paddedY - headerHeight) / mDayHeight;
996ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        final int col = (paddedXRtl * DAYS_IN_WEEK) / mPaddedWidth;
9970ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int index = col + row * DAYS_IN_WEEK;
9980ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int day = index + 1 - findDayOffset();
9995c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette        if (!isValidDayOfMonth(day)) {
10000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return -1;
1001bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1002bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
10030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        return day;
1004bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
1005bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1006bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
10070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * Calculates the bounds of the specified day.
1008bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
100978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette     * @param id the day of the month
10100ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param outBounds the rect to populate with bounds
1011bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
101278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette    private boolean getBoundsForDay(int id, Rect outBounds) {
10135c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette        if (!isValidDayOfMonth(id)) {
10140ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            return false;
1015bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1016bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
101760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int index = id - 1 + findDayOffset();
101860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
1019ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        // Compute left edge, taking into account RTL.
10200ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int col = index % DAYS_IN_WEEK;
1021f63757b7a6d06138860640f8e95326dc1ff47646Alan Viverette        final int colWidth = mCellWidth;
1022ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        final int left;
1023ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        if (isLayoutRtl()) {
1024ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            left = getWidth() - getPaddingRight() - (col + 1) * colWidth;
1025ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        } else {
1026ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette            left = getPaddingLeft() + col * colWidth;
1027ddf655c49f4173aa55c9ba1a2622cf75cf5bc2f2Alan Viverette        }
10280ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
102960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        // Compute top edge.
103060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int row = index / DAYS_IN_WEEK;
103160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int rowHeight = mDayHeight;
10320ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette        final int headerHeight = mMonthHeight + mDayOfWeekHeight;
103360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        final int top = getPaddingTop() + headerHeight + row * rowHeight;
10340ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
103560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        outBounds.set(left, top, left + colWidth, top + rowHeight);
1036bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
103778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        return true;
103860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
103960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
104060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    /**
1041bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Called when the user clicks on a day. Handles callbacks to the
1042bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * {@link OnDayClickListener} if one is set.
1043bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
10440ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette     * @param day the day that was clicked
1045bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
104660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    private boolean onDayClicked(int day) {
10475c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette        if (!isValidDayOfMonth(day) || !isDayEnabled(day)) {
104860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return false;
104960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
105060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
1051bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (mOnDayClickListener != null) {
105260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            final Calendar date = Calendar.getInstance();
1053bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            date.set(mYear, mMonth, day);
1054bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mOnDayClickListener.onDayClick(this, date);
1055bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1056bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1057bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // This is a no-op if accessibility is turned off.
1058bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
105960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        return true;
106060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    }
106160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
106260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette    /**
1063bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Provides a virtual view hierarchy for interfacing with an accessibility
1064bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * service.
1065bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
1066bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private class MonthViewTouchHelper extends ExploreByTouchHelper {
1067bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private static final String DATE_FORMAT = "dd MMMM yyyy";
1068bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1069bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private final Rect mTempRect = new Rect();
1070bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private final Calendar mTempCalendar = Calendar.getInstance();
1071bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1072bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        public MonthViewTouchHelper(View host) {
1073bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            super(host);
1074bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1075bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1076bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
1077bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected int getVirtualViewAt(float x, float y) {
107878bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette            final int day = getDayAtLocation((int) (x + 0.5f), (int) (y + 0.5f));
10795c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            if (day != -1) {
1080bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return day;
1081bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
1082bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            return ExploreByTouchHelper.INVALID_ID;
1083bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1084bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1085bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
1086ffb46bf2956d89e3190007ccf2ef3ce3eed005feAlan Viverette        protected void getVisibleVirtualViews(IntArray virtualViewIds) {
10870ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            for (int day = 1; day <= mDaysInMonth; day++) {
1088bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                virtualViewIds.add(day);
1089bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
1090bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1091bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1092bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
1093bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
109478bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette            event.setContentDescription(getDayDescription(virtualViewId));
1095bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1096bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1097bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
1098bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
109978bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette            final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect);
11000ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette
11010ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            if (!hasBounds) {
11020ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                // The day is invalid, kill the node.
11030ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                mTempRect.setEmpty();
11040ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                node.setContentDescription("");
11050ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                node.setBoundsInParent(mTempRect);
11060ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                node.setVisibleToUser(false);
11070ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette                return;
11080ef59ac0e57e9b99d174d4a53f7d9639357743acAlan Viverette            }
1109bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
111078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette            node.setText(getDayText(virtualViewId));
111178bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette            node.setContentDescription(getDayDescription(virtualViewId));
1112bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            node.setBoundsInParent(mTempRect);
11135c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette
11145c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            final boolean isDayEnabled = isDayEnabled(virtualViewId);
11155c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            if (isDayEnabled) {
11165c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette                node.addAction(AccessibilityAction.ACTION_CLICK);
11175c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            }
11185c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette
11195c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            node.setEnabled(isDayEnabled);
1120bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1121c5b95c20b6fd3f4e63147efb22dd19c657b17001Alan Viverette            if (virtualViewId == mActivatedDay) {
112260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                // TODO: This should use activated once that's supported.
112360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                node.setChecked(true);
1124bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
1125bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1126bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1127bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1128bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
1129bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
1130bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                Bundle arguments) {
1131bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            switch (action) {
1132bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                case AccessibilityNodeInfo.ACTION_CLICK:
113378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette                    return onDayClicked(virtualViewId);
1134bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
1135bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1136bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            return false;
1137bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1138bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1139bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        /**
114060b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * Generates a description for a given virtual view.
1141bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         *
114278bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette         * @param id the day to generate a description for
114360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * @return a description of the virtual view
1144bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         */
114578bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        private CharSequence getDayDescription(int id) {
11465c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            if (isValidDayOfMonth(id)) {
114760b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                mTempCalendar.set(mYear, mMonth, id);
114860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette                return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis());
114960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            }
1150bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
115160b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return "";
115260b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        }
115360b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette
115460b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette        /**
115560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * Generates displayed text for a given virtual view.
115660b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         *
115778bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette         * @param id the day to generate text for
115860b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         * @return the visible text of the virtual view
115960b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette         */
116078bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        private CharSequence getDayText(int id) {
11615c33949d0272c4dfc313cf8b56d28597e2ec2c62Alan Viverette            if (isValidDayOfMonth(id)) {
11621ea4715cecf8d1a17a21f3d21ea4df56519e5ea6Chet Haase                return mDayFormatter.format(id);
1163bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
1164bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
116560b674e07bf7346a673abd4a5f40bddeca16e7ffAlan Viverette            return null;
1166bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
1167bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
1168bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1169bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
1170bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Handles callbacks when the user clicks on a time object.
1171bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
1172bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public interface OnDayClickListener {
117378bf1d329a4c0210394f846be1fd1390314aefc0Alan Viverette        void onDayClick(SimpleMonthView view, Calendar day);
1174bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
1175bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio}
1176