DayPickerView.java revision 4612740ddc76b3518dc6d189d5f8b5b7f60e9d64
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
19bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.Context;
20bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.ColorStateList;
21bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.content.res.Configuration;
22bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.os.Bundle;
23bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.util.Log;
2450eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viveretteimport android.util.MathUtils;
25bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.View;
26bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.ViewConfiguration;
27bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityEvent;
28bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport android.view.accessibility.AccessibilityNodeInfo;
29bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
30bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.text.SimpleDateFormat;
31bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Calendar;
32bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglioimport java.util.Locale;
33bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
34bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio/**
35bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio * This displays a list of months in a calendar format with selectable days.
36bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio */
37e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viveretteclass DayPickerView extends ListView implements AbsListView.OnScrollListener {
38bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static final String TAG = "DayPickerView";
39bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
40bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    // How long the GoTo fling animation should last
41bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static final int GOTO_SCROLL_DURATION = 250;
42bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
43bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    // How long to wait after receiving an onScrollStateChanged notification before acting on it
44bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private static final int SCROLL_CHANGE_DELAY = 40;
45bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
46e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    // so that the top line will be under the separator
47e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    private static final int LIST_TOP_OFFSET = -1;
48bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
49e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    private final SimpleMonthAdapter mAdapter = new SimpleMonthAdapter(getContext());
50e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette
51e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    private final ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(this);
52bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
53e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    private SimpleDateFormat mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
54bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
55bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    // highlighted time
56bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private Calendar mSelectedDay = Calendar.getInstance();
57bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private Calendar mTempDay = Calendar.getInstance();
5850eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    private Calendar mMinDate = Calendar.getInstance();
5950eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    private Calendar mMaxDate = Calendar.getInstance();
6050eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette
614612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    private Calendar mTempCalendar;
624612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
63e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    private OnDaySelectedListener mOnDaySelectedListener;
64bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
65bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    // which month should be displayed/highlighted [0-11]
66bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mCurrentMonthDisplayed;
67bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    // used for tracking what state listview is in
68bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
69bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    // used for tracking what state listview is in
70bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
71bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
72bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private boolean mPerformingScroll;
73bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
74e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    public DayPickerView(Context context) {
75bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        super(context);
7650eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette
77bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setAdapter(mAdapter);
78bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
79bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setDrawSelectorOnTop(false);
80bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setUpListView();
81e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette
824612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        goTo(mSelectedDay.getTimeInMillis(), false, false, true);
83e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette
84e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        mAdapter.setOnDaySelectedListener(mProxyOnDaySelectedListener);
85e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    }
86e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette
874612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    /**
884612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     * Sets the currently selected date to the specified timestamp. Jumps
894612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     * immediately to the new date. To animate to the new date, use
904612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     * {@link #setDate(long, boolean, boolean)}.
914612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     *
924612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     * @param timeInMillis
934612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     */
944612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public void setDate(long timeInMillis) {
954612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        setDate(timeInMillis, false, true);
964612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
974612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
984612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public void setDate(long timeInMillis, boolean animate, boolean forceScroll) {
994612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        goTo(timeInMillis, animate, true, forceScroll);
1004612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
1014612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
1024612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public long getDate() {
1034612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        return mSelectedDay.getTimeInMillis();
104e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    }
105e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette
106e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    public void setFirstDayOfWeek(int firstDayOfWeek) {
107e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        mAdapter.setFirstDayOfWeek(firstDayOfWeek);
108bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
109bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1104612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public int getFirstDayOfWeek() {
1114612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        return mAdapter.getFirstDayOfWeek();
1124612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
1134612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
1144612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public void setMinDate(long timeInMillis) {
1154612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        mMinDate.setTimeInMillis(timeInMillis);
1164612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        onRangeChanged();
1174612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
1184612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
1194612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public long getMinDate() {
1204612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        return mMinDate.getTimeInMillis();
1214612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
1224612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
1234612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public void setMaxDate(long timeInMillis) {
1244612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        mMaxDate.setTimeInMillis(timeInMillis);
1254612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        onRangeChanged();
1264612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
12750eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette
1284612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public long getMaxDate() {
1294612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        return mMaxDate.getTimeInMillis();
1304612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
1314612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
1324612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    /**
1334612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     * Handles changes to date range.
1344612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette     */
1354612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    public void onRangeChanged() {
13650eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        mAdapter.setRange(mMinDate, mMaxDate);
13750eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette
1385ecbfeb38b6bdcfe8f3561f8cdcb4af9ba30c886Alan Viverette        // Changing the min/max date changes the selection position since we
1394612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        // don't really have stable IDs. Jumps immediately to the new position.
1404612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        goTo(mSelectedDay.getTimeInMillis(), false, false, true);
14150eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    }
14250eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette
14350eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    /**
144e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette     * Sets the listener to call when the user selects a day.
14550eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette     *
146e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette     * @param listener The listener to call.
147bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
148e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    public void setOnDaySelectedListener(OnDaySelectedListener listener) {
149e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        mOnDaySelectedListener = listener;
150bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
151bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
152bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /*
153bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Sets all the required fields for the list view. Override this method to
154bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * set a different list view behavior.
155bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
156e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    private void setUpListView() {
157bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Transparent background on scroll
158bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setCacheColorHint(0);
159bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // No dividers
160bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setDivider(null);
161bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Items are clickable
162bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setItemsCanFocus(true);
163bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // The thumb gets in the way, so disable it
164bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setFastScrollEnabled(false);
165bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setVerticalScrollBarEnabled(false);
166bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setOnScrollListener(this);
167bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        setFadingEdgeLength(0);
168bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Make the scrolling behavior nicer
169e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        setFriction(ViewConfiguration.getScrollFriction());
170bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
171bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
17250eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette    private int getDiffMonths(Calendar start, Calendar end) {
173bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final int diffYears = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);
174bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final int diffMonths = end.get(Calendar.MONTH) - start.get(Calendar.MONTH) + 12 * diffYears;
175bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return diffMonths;
176bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
177bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1784612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    private int getPositionFromDay(long timeInMillis) {
17950eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        final int diffMonthMax = getDiffMonths(mMinDate, mMaxDate);
1804612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        final int diffMonth = getDiffMonths(mMinDate, getTempCalendarForTime(timeInMillis));
18150eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        return MathUtils.constrain(diffMonth, 0, diffMonthMax);
182bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
183bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
1844612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    private Calendar getTempCalendarForTime(long timeInMillis) {
1854612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        if (mTempCalendar == null) {
1864612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette            mTempCalendar = Calendar.getInstance();
1874612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        }
1884612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        mTempCalendar.setTimeInMillis(timeInMillis);
1894612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        return mTempCalendar;
1904612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
1914612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
192bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
193bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * This moves to the specified time in the view. If the time is not already
194bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * in range it will move the list so that the first of the month containing
195bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * the time is at the top of the view. If the new time is already in view
196bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * the list will not be scrolled unless forceScroll is true. This time may
197bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * optionally be highlighted as selected as well.
198bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
199bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @param day The day to move to
200bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @param animate Whether to scroll to the given time or just redraw at the
201bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *            new location
202bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @param setSelected Whether to set the given time as selected
203bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @param forceScroll Whether to recenter even if the time is already
204bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *            visible
205bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @return Whether or not the view animated to the new location
206bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
2074612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    private boolean goTo(long day, boolean animate, boolean setSelected, boolean forceScroll) {
208bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
209bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Set the selected day
210bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (setSelected) {
2114612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette            mSelectedDay.setTimeInMillis(day);
212bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
213bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
2144612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        mTempDay.setTimeInMillis(day);
215bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final int position = getPositionFromDay(day);
216bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
217bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        View child;
218bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        int i = 0;
219bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        int top = 0;
220bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Find a child that's completely in the view
221bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        do {
222bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            child = getChildAt(i++);
223bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (child == null) {
224bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                break;
225bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
226bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            top = child.getTop();
227bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        } while (top < 0);
228bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
229bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Compute the first and last position visible
230bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        int selectedPosition;
231bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (child != null) {
232bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            selectedPosition = getPositionForView(child);
233bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        } else {
234bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            selectedPosition = 0;
235bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
236bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
237bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (setSelected) {
238bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mAdapter.setSelectedDay(mSelectedDay);
239bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
240bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
241bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Check if the selected day is now outside of our visible range
242bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // and if so scroll to the month that contains it
243bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (position != selectedPosition || forceScroll) {
244bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            setMonthDisplayed(mTempDay);
245bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
246bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (animate) {
247bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                smoothScrollToPositionFromTop(
248bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                        position, LIST_TOP_OFFSET, GOTO_SCROLL_DURATION);
249bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                return true;
250bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            } else {
251bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                postSetSelection(position);
252bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
253bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        } else if (setSelected) {
254bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            setMonthDisplayed(mSelectedDay);
255bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
256bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return false;
257bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
258bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
259bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public void postSetSelection(final int position) {
260bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        clearFocus();
261bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        post(new Runnable() {
262bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
263bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            @Override
264bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            public void run() {
265bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                setSelection(position);
266bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
267bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        });
268bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        onScrollStateChanged(this, OnScrollListener.SCROLL_STATE_IDLE);
269bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
270bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
271bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
272bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Updates the title and selected month if the view has moved to a new
273bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * month.
274bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
275bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
276bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
277bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                         int totalItemCount) {
278bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        SimpleMonthView child = (SimpleMonthView) view.getChildAt(0);
279bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (child == null) {
280bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            return;
281bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
282bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
283bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mPreviousScrollState = mCurrentScrollState;
284bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
285bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
286bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
287bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Sets the month displayed at the top of this view based on time. Override
288bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * to add custom events when the title is changed.
289bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
290bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected void setMonthDisplayed(Calendar date) {
291bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (mCurrentMonthDisplayed != date.get(Calendar.MONTH)) {
292bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mCurrentMonthDisplayed = date.get(Calendar.MONTH);
293bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            invalidateViews();
294bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
295bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
296bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
297bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
298bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public void onScrollStateChanged(AbsListView view, int scrollState) {
299bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // use a post to prevent re-entering onScrollStateChanged before it
300bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // exits
301bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
302bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
303bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
304bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    void setCalendarTextColor(ColorStateList colors) {
305bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mAdapter.setCalendarTextColor(colors);
306bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
307bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
3084612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    void setCalendarTextAppearance(int resId) {
3094612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        mAdapter.setCalendarTextAppearance(resId);
3104612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette    }
3114612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette
312bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected class ScrollStateRunnable implements Runnable {
313bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private int mNewState;
314bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        private View mParent;
315bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
316bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        ScrollStateRunnable(View view) {
317bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mParent = view;
318bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
319bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
320bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        /**
321bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         * Sets up the runnable with a short delay in case the scroll state
322bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         * immediately changes again.
323bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         *
324bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         * @param view The list view that changed state
325bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         * @param scrollState The new state it changed to
326bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio         */
327bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        public void doScrollStateChange(AbsListView view, int scrollState) {
328bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mParent.removeCallbacks(this);
329bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mNewState = scrollState;
330bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mParent.postDelayed(this, SCROLL_CHANGE_DELAY);
331bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
332bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
333bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        @Override
334bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        public void run() {
335bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mCurrentScrollState = mNewState;
336bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (Log.isLoggable(TAG, Log.DEBUG)) {
337bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                Log.d(TAG,
338bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                        "new scroll state: " + mNewState + " old state: " + mPreviousScrollState);
339bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
340bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            // Fix the position after a scroll or a fling ends
341bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
342bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE
343bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    && mPreviousScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
344bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                mPreviousScrollState = mNewState;
345bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                int i = 0;
346bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                View child = getChildAt(i);
347bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                while (child != null && child.getBottom() <= 0) {
348bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    child = getChildAt(++i);
349bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
350bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                if (child == null) {
351bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    // The view is no longer visible, just return
352bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    return;
353bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
354bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                int firstPosition = getFirstVisiblePosition();
355bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                int lastPosition = getLastVisiblePosition();
356bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                boolean scroll = firstPosition != 0 && lastPosition != getCount() - 1;
357bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                final int top = child.getTop();
358bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                final int bottom = child.getBottom();
359bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                final int midpoint = getHeight() / 2;
360bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                if (scroll && top < LIST_TOP_OFFSET) {
361bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    if (bottom > midpoint) {
362bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                        smoothScrollBy(top, GOTO_SCROLL_DURATION);
363bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    } else {
364bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                        smoothScrollBy(bottom, GOTO_SCROLL_DURATION);
365bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    }
366bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
367bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            } else {
368bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                mPreviousScrollState = mNewState;
369bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
370bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
371bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
372bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
373bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
374bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Gets the position of the view that is most prominently displayed within the list view.
375bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
376bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public int getMostVisiblePosition() {
377bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final int firstPosition = getFirstVisiblePosition();
378bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final int height = getHeight();
379bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
380bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        int maxDisplayedHeight = 0;
381bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        int mostVisibleIndex = 0;
382bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        int i=0;
383bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        int bottom = 0;
384bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        while (bottom < height) {
385bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            View child = getChildAt(i);
386bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (child == null) {
387bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                break;
388bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
389bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            bottom = child.getBottom();
390bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            int displayedHeight = Math.min(bottom, height) - Math.max(0, child.getTop());
391bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (displayedHeight > maxDisplayedHeight) {
392bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                mostVisibleIndex = i;
393bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                maxDisplayedHeight = displayedHeight;
394bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
395bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            i++;
396bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
397bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return firstPosition + mostVisibleIndex;
398bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
399bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
400bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
401bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Attempts to return the date that has accessibility focus.
402bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
403bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @return The date that has accessibility focus, or {@code null} if no date
404bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *         has focus.
405bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
406bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private Calendar findAccessibilityFocus() {
407bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final int childCount = getChildCount();
408bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        for (int i = 0; i < childCount; i++) {
409bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            final View child = getChildAt(i);
410bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (child instanceof SimpleMonthView) {
411bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                final Calendar focus = ((SimpleMonthView) child).getAccessibilityFocus();
412bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                if (focus != null) {
413bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    return focus;
414bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
415bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
416bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
417bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
418bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return null;
419bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
420bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
421bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
422bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Attempts to restore accessibility focus to a given date. No-op if
423bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * {@code day} is {@code null}.
424bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     *
425bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @param day The date that should receive accessibility focus
426bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * @return {@code true} if focus was restored
427bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
428bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private boolean restoreAccessibilityFocus(Calendar day) {
429bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (day == null) {
430bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            return false;
431bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
432bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
433bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final int childCount = getChildCount();
434bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        for (int i = 0; i < childCount; i++) {
435bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            final View child = getChildAt(i);
436bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (child instanceof SimpleMonthView) {
437bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                if (((SimpleMonthView) child).restoreAccessibilityFocus(day)) {
438bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    return true;
439bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
440bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
441bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
442bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
443bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return false;
444bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
445bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
446bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
447bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected void layoutChildren() {
448bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        final Calendar focusedDay = findAccessibilityFocus();
449bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        super.layoutChildren();
450bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (mPerformingScroll) {
451bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            mPerformingScroll = false;
452bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        } else {
453bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            restoreAccessibilityFocus(focusedDay);
454bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
455bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
456bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
457bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
458bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    protected void onConfigurationChanged(Configuration newConfig) {
459bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
460bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
461bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
462bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
463bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
464bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        super.onInitializeAccessibilityEvent(event);
465bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        event.setItemCount(-1);
466bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
467bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
468bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    private String getMonthAndYearString(Calendar day) {
4694612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        final StringBuilder sbuf = new StringBuilder();
470bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        sbuf.append(day.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()));
471bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        sbuf.append(" ");
472bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        sbuf.append(mYearFormat.format(day.getTime()));
473bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return sbuf.toString();
474bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
475bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
476bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
477bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * Necessary for accessibility, to ensure we support "scrolling" forward and backward
478bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * in the month list.
479bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
480bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
481bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
482bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        super.onInitializeAccessibilityNodeInfo(info);
4834612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
4844612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
485bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
486bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
487bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    /**
488bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     * When scroll forward/backward events are received, announce the newly scrolled-to month.
489bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio     */
490bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    @Override
491bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    public boolean performAccessibilityAction(int action, Bundle arguments) {
492bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
493bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
494bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            return super.performAccessibilityAction(action, arguments);
495bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
496bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
497bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Figure out what month is showing.
49850eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        final int firstVisiblePosition = getFirstVisiblePosition();
49950eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        final int month = firstVisiblePosition % 12;
50050eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        final int year = firstVisiblePosition / 12 + mMinDate.get(Calendar.YEAR);
50150eb025c2fea7e364e0be951ce8ba6ca605f901aAlan Viverette        final Calendar day = Calendar.getInstance();
502bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        day.set(year, month, 1);
503bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
504bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Scroll either forward or backward one month.
505bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
506bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            day.add(Calendar.MONTH, 1);
507bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (day.get(Calendar.MONTH) == 12) {
508bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                day.set(Calendar.MONTH, 0);
509bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                day.add(Calendar.YEAR, 1);
510bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
511bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
512bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            View firstVisibleView = getChildAt(0);
513bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            // If the view is fully visible, jump one month back. Otherwise, we'll just jump
514bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            // to the first day of first visible month.
515bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
516bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                // There's an off-by-one somewhere, so the top of the first visible item will
517bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                // actually be -1 when it's at the exact top.
518bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                day.add(Calendar.MONTH, -1);
519bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                if (day.get(Calendar.MONTH) == -1) {
520bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    day.set(Calendar.MONTH, 11);
521bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                    day.add(Calendar.YEAR, -1);
522bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio                }
523bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio            }
524bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        }
525bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio
526bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        // Go to that month.
527bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        announceForAccessibility(getMonthAndYearString(day));
5284612740ddc76b3518dc6d189d5f8b5b7f60e9d64Alan Viverette        goTo(day.getTimeInMillis(), true, false, true);
529bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        mPerformingScroll = true;
530bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio        return true;
531bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio    }
532e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette
533e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    public interface OnDaySelectedListener {
534e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        public void onDaySelected(DayPickerView view, Calendar day);
535e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    }
536e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette
537e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    private final SimpleMonthAdapter.OnDaySelectedListener
538e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette            mProxyOnDaySelectedListener = new SimpleMonthAdapter.OnDaySelectedListener() {
539e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        @Override
540e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        public void onDaySelected(SimpleMonthAdapter adapter, Calendar day) {
541e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette            if (mOnDaySelectedListener != null) {
542e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette                mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
543e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette            }
544e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette        }
545e763c9bd6ed0ca46daafc21fc4313ebcad4bcafaAlan Viverette    };
546bd9152f6ee156ee473f05f6f05f238605996fca4Fabrice Di Meglio}
547