Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.app.Service;
     20 import android.content.Context;
     21 import android.content.res.Configuration;
     22 import android.content.res.TypedArray;
     23 import android.database.DataSetObserver;
     24 import android.graphics.Canvas;
     25 import android.graphics.Paint;
     26 import android.graphics.Rect;
     27 import android.graphics.drawable.Drawable;
     28 import android.icu.util.Calendar;
     29 import android.text.format.DateUtils;
     30 import android.util.AttributeSet;
     31 import android.util.DisplayMetrics;
     32 import android.util.TypedValue;
     33 import android.view.GestureDetector;
     34 import android.view.LayoutInflater;
     35 import android.view.MotionEvent;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 
     39 import com.android.internal.R;
     40 
     41 import libcore.icu.LocaleData;
     42 
     43 import java.util.Locale;
     44 
     45 /**
     46  * A delegate implementing the legacy CalendarView
     47  */
     48 class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelegate {
     49     /**
     50      * Default value whether to show week number.
     51      */
     52     private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
     53 
     54     /**
     55      * The number of milliseconds in a day.e
     56      */
     57     private static final long MILLIS_IN_DAY = 86400000L;
     58 
     59     /**
     60      * The number of day in a week.
     61      */
     62     private static final int DAYS_PER_WEEK = 7;
     63 
     64     /**
     65      * The number of milliseconds in a week.
     66      */
     67     private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
     68 
     69     /**
     70      * Affects when the month selection will change while scrolling upe
     71      */
     72     private static final int SCROLL_HYST_WEEKS = 2;
     73 
     74     /**
     75      * How long the GoTo fling animation should last.
     76      */
     77     private static final int GOTO_SCROLL_DURATION = 1000;
     78 
     79     /**
     80      * The duration of the adjustment upon a user scroll in milliseconds.
     81      */
     82     private static final int ADJUSTMENT_SCROLL_DURATION = 500;
     83 
     84     /**
     85      * How long to wait after receiving an onScrollStateChanged notification
     86      * before acting on it.
     87      */
     88     private static final int SCROLL_CHANGE_DELAY = 40;
     89 
     90     private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
     91 
     92     private static final int DEFAULT_DATE_TEXT_SIZE = 14;
     93 
     94     private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
     95 
     96     private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
     97 
     98     private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
     99 
    100     private static final int UNSCALED_BOTTOM_BUFFER = 20;
    101 
    102     private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
    103 
    104     private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
    105 
    106     private final int mWeekSeparatorLineWidth;
    107 
    108     private int mDateTextSize;
    109 
    110     private Drawable mSelectedDateVerticalBar;
    111 
    112     private final int mSelectedDateVerticalBarWidth;
    113 
    114     private int mSelectedWeekBackgroundColor;
    115 
    116     private int mFocusedMonthDateColor;
    117 
    118     private int mUnfocusedMonthDateColor;
    119 
    120     private int mWeekSeparatorLineColor;
    121 
    122     private int mWeekNumberColor;
    123 
    124     private int mWeekDayTextAppearanceResId;
    125 
    126     private int mDateTextAppearanceResId;
    127 
    128     /**
    129      * The top offset of the weeks list.
    130      */
    131     private int mListScrollTopOffset = 2;
    132 
    133     /**
    134      * The visible height of a week view.
    135      */
    136     private int mWeekMinVisibleHeight = 12;
    137 
    138     /**
    139      * The visible height of a week view.
    140      */
    141     private int mBottomBuffer = 20;
    142 
    143     /**
    144      * The number of shown weeks.
    145      */
    146     private int mShownWeekCount;
    147 
    148     /**
    149      * Flag whether to show the week number.
    150      */
    151     private boolean mShowWeekNumber;
    152 
    153     /**
    154      * The number of day per week to be shown.
    155      */
    156     private int mDaysPerWeek = 7;
    157 
    158     /**
    159      * The friction of the week list while flinging.
    160      */
    161     private float mFriction = .05f;
    162 
    163     /**
    164      * Scale for adjusting velocity of the week list while flinging.
    165      */
    166     private float mVelocityScale = 0.333f;
    167 
    168     /**
    169      * The adapter for the weeks list.
    170      */
    171     private WeeksAdapter mAdapter;
    172 
    173     /**
    174      * The weeks list.
    175      */
    176     private ListView mListView;
    177 
    178     /**
    179      * The name of the month to display.
    180      */
    181     private TextView mMonthName;
    182 
    183     /**
    184      * The header with week day names.
    185      */
    186     private ViewGroup mDayNamesHeader;
    187 
    188     /**
    189      * Cached abbreviations for day of week names.
    190      */
    191     private String[] mDayNamesShort;
    192 
    193     /**
    194      * Cached full-length day of week names.
    195      */
    196     private String[] mDayNamesLong;
    197 
    198     /**
    199      * The first day of the week.
    200      */
    201     private int mFirstDayOfWeek;
    202 
    203     /**
    204      * Which month should be displayed/highlighted [0-11].
    205      */
    206     private int mCurrentMonthDisplayed = -1;
    207 
    208     /**
    209      * Used for tracking during a scroll.
    210      */
    211     private long mPreviousScrollPosition;
    212 
    213     /**
    214      * Used for tracking which direction the view is scrolling.
    215      */
    216     private boolean mIsScrollingUp = false;
    217 
    218     /**
    219      * The previous scroll state of the weeks ListView.
    220      */
    221     private int mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
    222 
    223     /**
    224      * The current scroll state of the weeks ListView.
    225      */
    226     private int mCurrentScrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
    227 
    228     /**
    229      * Listener for changes in the selected day.
    230      */
    231     private CalendarView.OnDateChangeListener mOnDateChangeListener;
    232 
    233     /**
    234      * Command for adjusting the position after a scroll/fling.
    235      */
    236     private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
    237 
    238     /**
    239      * Temporary instance to avoid multiple instantiations.
    240      */
    241     private Calendar mTempDate;
    242 
    243     /**
    244      * The first day of the focused month.
    245      */
    246     private Calendar mFirstDayOfMonth;
    247 
    248     /**
    249      * The start date of the range supported by this picker.
    250      */
    251     private Calendar mMinDate;
    252 
    253     /**
    254      * The end date of the range supported by this picker.
    255      */
    256     private Calendar mMaxDate;
    257 
    258     CalendarViewLegacyDelegate(CalendarView delegator, Context context, AttributeSet attrs,
    259             int defStyleAttr, int defStyleRes) {
    260         super(delegator, context);
    261 
    262         final TypedArray a = context.obtainStyledAttributes(attrs,
    263                 R.styleable.CalendarView, defStyleAttr, defStyleRes);
    264         mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber,
    265                 DEFAULT_SHOW_WEEK_NUMBER);
    266         mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek,
    267                 LocaleData.get(Locale.getDefault()).firstDayOfWeek);
    268         final String minDate = a.getString(R.styleable.CalendarView_minDate);
    269         if (!CalendarView.parseDate(minDate, mMinDate)) {
    270             CalendarView.parseDate(DEFAULT_MIN_DATE, mMinDate);
    271         }
    272         final String maxDate = a.getString(R.styleable.CalendarView_maxDate);
    273         if (!CalendarView.parseDate(maxDate, mMaxDate)) {
    274             CalendarView.parseDate(DEFAULT_MAX_DATE, mMaxDate);
    275         }
    276         if (mMaxDate.before(mMinDate)) {
    277             throw new IllegalArgumentException("Max date cannot be before min date.");
    278         }
    279         mShownWeekCount = a.getInt(R.styleable.CalendarView_shownWeekCount,
    280                 DEFAULT_SHOWN_WEEK_COUNT);
    281         mSelectedWeekBackgroundColor = a.getColor(
    282                 R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
    283         mFocusedMonthDateColor = a.getColor(
    284                 R.styleable.CalendarView_focusedMonthDateColor, 0);
    285         mUnfocusedMonthDateColor = a.getColor(
    286                 R.styleable.CalendarView_unfocusedMonthDateColor, 0);
    287         mWeekSeparatorLineColor = a.getColor(
    288                 R.styleable.CalendarView_weekSeparatorLineColor, 0);
    289         mWeekNumberColor = a.getColor(R.styleable.CalendarView_weekNumberColor, 0);
    290         mSelectedDateVerticalBar = a.getDrawable(
    291                 R.styleable.CalendarView_selectedDateVerticalBar);
    292 
    293         mDateTextAppearanceResId = a.getResourceId(
    294                 R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
    295         updateDateTextSize();
    296 
    297         mWeekDayTextAppearanceResId = a.getResourceId(
    298                 R.styleable.CalendarView_weekDayTextAppearance,
    299                 DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
    300         a.recycle();
    301 
    302         DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
    303         mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    304                 UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
    305         mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    306                 UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
    307         mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    308                 UNSCALED_BOTTOM_BUFFER, displayMetrics);
    309         mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    310                 UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
    311         mWeekSeparatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    312                 UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
    313 
    314         LayoutInflater layoutInflater = (LayoutInflater) mContext
    315                 .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
    316         View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
    317         mDelegator.addView(content);
    318 
    319         mListView = mDelegator.findViewById(R.id.list);
    320         mDayNamesHeader = content.findViewById(R.id.day_names);
    321         mMonthName = content.findViewById(R.id.month_name);
    322 
    323         setUpHeader();
    324         setUpListView();
    325         setUpAdapter();
    326 
    327         // go to today or whichever is close to today min or max date
    328         mTempDate.setTimeInMillis(System.currentTimeMillis());
    329         if (mTempDate.before(mMinDate)) {
    330             goTo(mMinDate, false, true, true);
    331         } else if (mMaxDate.before(mTempDate)) {
    332             goTo(mMaxDate, false, true, true);
    333         } else {
    334             goTo(mTempDate, false, true, true);
    335         }
    336 
    337         mDelegator.invalidate();
    338     }
    339 
    340     @Override
    341     public void setShownWeekCount(int count) {
    342         if (mShownWeekCount != count) {
    343             mShownWeekCount = count;
    344             mDelegator.invalidate();
    345         }
    346     }
    347 
    348     @Override
    349     public int getShownWeekCount() {
    350         return mShownWeekCount;
    351     }
    352 
    353     @Override
    354     public void setSelectedWeekBackgroundColor(int color) {
    355         if (mSelectedWeekBackgroundColor != color) {
    356             mSelectedWeekBackgroundColor = color;
    357             final int childCount = mListView.getChildCount();
    358             for (int i = 0; i < childCount; i++) {
    359                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    360                 if (weekView.mHasSelectedDay) {
    361                     weekView.invalidate();
    362                 }
    363             }
    364         }
    365     }
    366 
    367     @Override
    368     public int getSelectedWeekBackgroundColor() {
    369         return mSelectedWeekBackgroundColor;
    370     }
    371 
    372     @Override
    373     public void setFocusedMonthDateColor(int color) {
    374         if (mFocusedMonthDateColor != color) {
    375             mFocusedMonthDateColor = color;
    376             final int childCount = mListView.getChildCount();
    377             for (int i = 0; i < childCount; i++) {
    378                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    379                 if (weekView.mHasFocusedDay) {
    380                     weekView.invalidate();
    381                 }
    382             }
    383         }
    384     }
    385 
    386     @Override
    387     public int getFocusedMonthDateColor() {
    388         return mFocusedMonthDateColor;
    389     }
    390 
    391     @Override
    392     public void setUnfocusedMonthDateColor(int color) {
    393         if (mUnfocusedMonthDateColor != color) {
    394             mUnfocusedMonthDateColor = color;
    395             final int childCount = mListView.getChildCount();
    396             for (int i = 0; i < childCount; i++) {
    397                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    398                 if (weekView.mHasUnfocusedDay) {
    399                     weekView.invalidate();
    400                 }
    401             }
    402         }
    403     }
    404 
    405     @Override
    406     public int getUnfocusedMonthDateColor() {
    407         return mUnfocusedMonthDateColor;
    408     }
    409 
    410     @Override
    411     public void setWeekNumberColor(int color) {
    412         if (mWeekNumberColor != color) {
    413             mWeekNumberColor = color;
    414             if (mShowWeekNumber) {
    415                 invalidateAllWeekViews();
    416             }
    417         }
    418     }
    419 
    420     @Override
    421     public int getWeekNumberColor() {
    422         return mWeekNumberColor;
    423     }
    424 
    425     @Override
    426     public void setWeekSeparatorLineColor(int color) {
    427         if (mWeekSeparatorLineColor != color) {
    428             mWeekSeparatorLineColor = color;
    429             invalidateAllWeekViews();
    430         }
    431     }
    432 
    433     @Override
    434     public int getWeekSeparatorLineColor() {
    435         return mWeekSeparatorLineColor;
    436     }
    437 
    438     @Override
    439     public void setSelectedDateVerticalBar(int resourceId) {
    440         Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
    441         setSelectedDateVerticalBar(drawable);
    442     }
    443 
    444     @Override
    445     public void setSelectedDateVerticalBar(Drawable drawable) {
    446         if (mSelectedDateVerticalBar != drawable) {
    447             mSelectedDateVerticalBar = drawable;
    448             final int childCount = mListView.getChildCount();
    449             for (int i = 0; i < childCount; i++) {
    450                 WeekView weekView = (WeekView) mListView.getChildAt(i);
    451                 if (weekView.mHasSelectedDay) {
    452                     weekView.invalidate();
    453                 }
    454             }
    455         }
    456     }
    457 
    458     @Override
    459     public Drawable getSelectedDateVerticalBar() {
    460         return mSelectedDateVerticalBar;
    461     }
    462 
    463     @Override
    464     public void setWeekDayTextAppearance(int resourceId) {
    465         if (mWeekDayTextAppearanceResId != resourceId) {
    466             mWeekDayTextAppearanceResId = resourceId;
    467             setUpHeader();
    468         }
    469     }
    470 
    471     @Override
    472     public int getWeekDayTextAppearance() {
    473         return mWeekDayTextAppearanceResId;
    474     }
    475 
    476     @Override
    477     public void setDateTextAppearance(int resourceId) {
    478         if (mDateTextAppearanceResId != resourceId) {
    479             mDateTextAppearanceResId = resourceId;
    480             updateDateTextSize();
    481             invalidateAllWeekViews();
    482         }
    483     }
    484 
    485     @Override
    486     public int getDateTextAppearance() {
    487         return mDateTextAppearanceResId;
    488     }
    489 
    490     @Override
    491     public void setMinDate(long minDate) {
    492         mTempDate.setTimeInMillis(minDate);
    493         if (isSameDate(mTempDate, mMinDate)) {
    494             return;
    495         }
    496         mMinDate.setTimeInMillis(minDate);
    497         // make sure the current date is not earlier than
    498         // the new min date since the latter is used for
    499         // calculating the indices in the adapter thus
    500         // avoiding out of bounds error
    501         Calendar date = mAdapter.mSelectedDate;
    502         if (date.before(mMinDate)) {
    503             mAdapter.setSelectedDay(mMinDate);
    504         }
    505         // reinitialize the adapter since its range depends on min date
    506         mAdapter.init();
    507         if (date.before(mMinDate)) {
    508             setDate(mTempDate.getTimeInMillis());
    509         } else {
    510             // we go to the current date to force the ListView to query its
    511             // adapter for the shown views since we have changed the adapter
    512             // range and the base from which the later calculates item indices
    513             // note that calling setDate will not work since the date is the same
    514             goTo(date, false, true, false);
    515         }
    516     }
    517 
    518     @Override
    519     public long getMinDate() {
    520         return mMinDate.getTimeInMillis();
    521     }
    522 
    523     @Override
    524     public void setMaxDate(long maxDate) {
    525         mTempDate.setTimeInMillis(maxDate);
    526         if (isSameDate(mTempDate, mMaxDate)) {
    527             return;
    528         }
    529         mMaxDate.setTimeInMillis(maxDate);
    530         // reinitialize the adapter since its range depends on max date
    531         mAdapter.init();
    532         Calendar date = mAdapter.mSelectedDate;
    533         if (date.after(mMaxDate)) {
    534             setDate(mMaxDate.getTimeInMillis());
    535         } else {
    536             // we go to the current date to force the ListView to query its
    537             // adapter for the shown views since we have changed the adapter
    538             // range and the base from which the later calculates item indices
    539             // note that calling setDate will not work since the date is the same
    540             goTo(date, false, true, false);
    541         }
    542     }
    543 
    544     @Override
    545     public long getMaxDate() {
    546         return mMaxDate.getTimeInMillis();
    547     }
    548 
    549     @Override
    550     public void setShowWeekNumber(boolean showWeekNumber) {
    551         if (mShowWeekNumber == showWeekNumber) {
    552             return;
    553         }
    554         mShowWeekNumber = showWeekNumber;
    555         mAdapter.notifyDataSetChanged();
    556         setUpHeader();
    557     }
    558 
    559     @Override
    560     public boolean getShowWeekNumber() {
    561         return mShowWeekNumber;
    562     }
    563 
    564     @Override
    565     public void setFirstDayOfWeek(int firstDayOfWeek) {
    566         if (mFirstDayOfWeek == firstDayOfWeek) {
    567             return;
    568         }
    569         mFirstDayOfWeek = firstDayOfWeek;
    570         mAdapter.init();
    571         mAdapter.notifyDataSetChanged();
    572         setUpHeader();
    573     }
    574 
    575     @Override
    576     public int getFirstDayOfWeek() {
    577         return mFirstDayOfWeek;
    578     }
    579 
    580     @Override
    581     public void setDate(long date) {
    582         setDate(date, false, false);
    583     }
    584 
    585     @Override
    586     public void setDate(long date, boolean animate, boolean center) {
    587         mTempDate.setTimeInMillis(date);
    588         if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
    589             return;
    590         }
    591         goTo(mTempDate, animate, true, center);
    592     }
    593 
    594     @Override
    595     public long getDate() {
    596         return mAdapter.mSelectedDate.getTimeInMillis();
    597     }
    598 
    599     @Override
    600     public void setOnDateChangeListener(CalendarView.OnDateChangeListener listener) {
    601         mOnDateChangeListener = listener;
    602     }
    603 
    604     @Override
    605     public boolean getBoundsForDate(long date, Rect outBounds) {
    606         Calendar calendarDate = Calendar.getInstance();
    607         calendarDate.setTimeInMillis(date);
    608         int listViewEntryCount = mListView.getCount();
    609         for (int i = 0; i < listViewEntryCount; i++) {
    610             WeekView currWeekView = (WeekView) mListView.getChildAt(i);
    611             if (currWeekView.getBoundsForDate(calendarDate, outBounds)) {
    612                 // Found the date in this week. Now need to offset vertically to return correct
    613                 // bounds in the coordinate system of the entire layout
    614                 final int[] weekViewPositionOnScreen = new int[2];
    615                 final int[] delegatorPositionOnScreen = new int[2];
    616                 currWeekView.getLocationOnScreen(weekViewPositionOnScreen);
    617                 mDelegator.getLocationOnScreen(delegatorPositionOnScreen);
    618                 final int extraVerticalOffset =
    619                         weekViewPositionOnScreen[1] - delegatorPositionOnScreen[1];
    620                 outBounds.top += extraVerticalOffset;
    621                 outBounds.bottom += extraVerticalOffset;
    622                 return true;
    623             }
    624         }
    625         return false;
    626     }
    627 
    628     @Override
    629     public void onConfigurationChanged(Configuration newConfig) {
    630         setCurrentLocale(newConfig.locale);
    631     }
    632 
    633     /**
    634      * Sets the current locale.
    635      *
    636      * @param locale The current locale.
    637      */
    638     @Override
    639     protected void setCurrentLocale(Locale locale) {
    640         super.setCurrentLocale(locale);
    641 
    642         mTempDate = getCalendarForLocale(mTempDate, locale);
    643         mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
    644         mMinDate = getCalendarForLocale(mMinDate, locale);
    645         mMaxDate = getCalendarForLocale(mMaxDate, locale);
    646     }
    647     private void updateDateTextSize() {
    648         TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
    649                 mDateTextAppearanceResId, R.styleable.TextAppearance);
    650         mDateTextSize = dateTextAppearance.getDimensionPixelSize(
    651                 R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
    652         dateTextAppearance.recycle();
    653     }
    654 
    655     /**
    656      * Invalidates all week views.
    657      */
    658     private void invalidateAllWeekViews() {
    659         final int childCount = mListView.getChildCount();
    660         for (int i = 0; i < childCount; i++) {
    661             View view = mListView.getChildAt(i);
    662             view.invalidate();
    663         }
    664     }
    665 
    666     /**
    667      * Gets a calendar for locale bootstrapped with the value of a given calendar.
    668      *
    669      * @param oldCalendar The old calendar.
    670      * @param locale The locale.
    671      */
    672     private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
    673         if (oldCalendar == null) {
    674             return Calendar.getInstance(locale);
    675         } else {
    676             final long currentTimeMillis = oldCalendar.getTimeInMillis();
    677             Calendar newCalendar = Calendar.getInstance(locale);
    678             newCalendar.setTimeInMillis(currentTimeMillis);
    679             return newCalendar;
    680         }
    681     }
    682 
    683     /**
    684      * @return True if the <code>firstDate</code> is the same as the <code>
    685      * secondDate</code>.
    686      */
    687     private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
    688         return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
    689                 && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
    690     }
    691 
    692     /**
    693      * Creates a new adapter if necessary and sets up its parameters.
    694      */
    695     private void setUpAdapter() {
    696         if (mAdapter == null) {
    697             mAdapter = new WeeksAdapter(mContext);
    698             mAdapter.registerDataSetObserver(new DataSetObserver() {
    699                 @Override
    700                 public void onChanged() {
    701                     if (mOnDateChangeListener != null) {
    702                         Calendar selectedDay = mAdapter.getSelectedDay();
    703                         mOnDateChangeListener.onSelectedDayChange(mDelegator,
    704                                 selectedDay.get(Calendar.YEAR),
    705                                 selectedDay.get(Calendar.MONTH),
    706                                 selectedDay.get(Calendar.DAY_OF_MONTH));
    707                     }
    708                 }
    709             });
    710             mListView.setAdapter(mAdapter);
    711         }
    712 
    713         // refresh the view with the new parameters
    714         mAdapter.notifyDataSetChanged();
    715     }
    716 
    717     /**
    718      * Sets up the strings to be used by the header.
    719      */
    720     private void setUpHeader() {
    721         mDayNamesShort = new String[mDaysPerWeek];
    722         mDayNamesLong = new String[mDaysPerWeek];
    723         for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
    724             int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
    725             mDayNamesShort[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
    726                     DateUtils.LENGTH_SHORTEST);
    727             mDayNamesLong[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
    728                     DateUtils.LENGTH_LONG);
    729         }
    730 
    731         TextView label = (TextView) mDayNamesHeader.getChildAt(0);
    732         if (mShowWeekNumber) {
    733             label.setVisibility(View.VISIBLE);
    734         } else {
    735             label.setVisibility(View.GONE);
    736         }
    737         for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
    738             label = (TextView) mDayNamesHeader.getChildAt(i);
    739             if (mWeekDayTextAppearanceResId > -1) {
    740                 label.setTextAppearance(mWeekDayTextAppearanceResId);
    741             }
    742             if (i < mDaysPerWeek + 1) {
    743                 label.setText(mDayNamesShort[i - 1]);
    744                 label.setContentDescription(mDayNamesLong[i - 1]);
    745                 label.setVisibility(View.VISIBLE);
    746             } else {
    747                 label.setVisibility(View.GONE);
    748             }
    749         }
    750         mDayNamesHeader.invalidate();
    751     }
    752 
    753     /**
    754      * Sets all the required fields for the list view.
    755      */
    756     private void setUpListView() {
    757         // Configure the listview
    758         mListView.setDivider(null);
    759         mListView.setItemsCanFocus(true);
    760         mListView.setVerticalScrollBarEnabled(false);
    761         mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
    762             public void onScrollStateChanged(AbsListView view, int scrollState) {
    763                 CalendarViewLegacyDelegate.this.onScrollStateChanged(view, scrollState);
    764             }
    765 
    766             public void onScroll(
    767                     AbsListView view, int firstVisibleItem, int visibleItemCount,
    768                     int totalItemCount) {
    769                 CalendarViewLegacyDelegate.this.onScroll(view, firstVisibleItem,
    770                         visibleItemCount, totalItemCount);
    771             }
    772         });
    773         // Make the scrolling behavior nicer
    774         mListView.setFriction(mFriction);
    775         mListView.setVelocityScale(mVelocityScale);
    776     }
    777 
    778     /**
    779      * This moves to the specified time in the view. If the time is not already
    780      * in range it will move the list so that the first of the month containing
    781      * the time is at the top of the view. If the new time is already in view
    782      * the list will not be scrolled unless forceScroll is true. This time may
    783      * optionally be highlighted as selected as well.
    784      *
    785      * @param date The time to move to.
    786      * @param animate Whether to scroll to the given time or just redraw at the
    787      *            new location.
    788      * @param setSelected Whether to set the given time as selected.
    789      * @param forceScroll Whether to recenter even if the time is already
    790      *            visible.
    791      *
    792      * @throws IllegalArgumentException if the provided date is before the
    793      *         range start or after the range end.
    794      */
    795     private void goTo(Calendar date, boolean animate, boolean setSelected,
    796             boolean forceScroll) {
    797         if (date.before(mMinDate) || date.after(mMaxDate)) {
    798             throw new IllegalArgumentException("timeInMillis must be between the values of "
    799                     + "getMinDate() and getMaxDate()");
    800         }
    801         // Find the first and last entirely visible weeks
    802         int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
    803         View firstChild = mListView.getChildAt(0);
    804         if (firstChild != null && firstChild.getTop() < 0) {
    805             firstFullyVisiblePosition++;
    806         }
    807         int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
    808         if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
    809             lastFullyVisiblePosition--;
    810         }
    811         if (setSelected) {
    812             mAdapter.setSelectedDay(date);
    813         }
    814         // Get the week we're going to
    815         int position = getWeeksSinceMinDate(date);
    816 
    817         // Check if the selected day is now outside of our visible range
    818         // and if so scroll to the month that contains it
    819         if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
    820                 || forceScroll) {
    821             mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
    822             mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
    823 
    824             setMonthDisplayed(mFirstDayOfMonth);
    825 
    826             // the earliest time we can scroll to is the min date
    827             if (mFirstDayOfMonth.before(mMinDate)) {
    828                 position = 0;
    829             } else {
    830                 position = getWeeksSinceMinDate(mFirstDayOfMonth);
    831             }
    832 
    833             mPreviousScrollState = AbsListView.OnScrollListener.SCROLL_STATE_FLING;
    834             if (animate) {
    835                 mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
    836                         GOTO_SCROLL_DURATION);
    837             } else {
    838                 mListView.setSelectionFromTop(position, mListScrollTopOffset);
    839                 // Perform any after scroll operations that are needed
    840                 onScrollStateChanged(mListView, AbsListView.OnScrollListener.SCROLL_STATE_IDLE);
    841             }
    842         } else if (setSelected) {
    843             // Otherwise just set the selection
    844             setMonthDisplayed(date);
    845         }
    846     }
    847 
    848     /**
    849      * Called when a <code>view</code> transitions to a new <code>scrollState
    850      * </code>.
    851      */
    852     private void onScrollStateChanged(AbsListView view, int scrollState) {
    853         mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
    854     }
    855 
    856     /**
    857      * Updates the title and selected month if the <code>view</code> has moved to a new
    858      * month.
    859      */
    860     private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    861                           int totalItemCount) {
    862         WeekView child = (WeekView) view.getChildAt(0);
    863         if (child == null) {
    864             return;
    865         }
    866 
    867         // Figure out where we are
    868         long currScroll =
    869                 view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
    870 
    871         // If we have moved since our last call update the direction
    872         if (currScroll < mPreviousScrollPosition) {
    873             mIsScrollingUp = true;
    874         } else if (currScroll > mPreviousScrollPosition) {
    875             mIsScrollingUp = false;
    876         } else {
    877             return;
    878         }
    879 
    880         // Use some hysteresis for checking which month to highlight. This
    881         // causes the month to transition when two full weeks of a month are
    882         // visible when scrolling up, and when the first day in a month reaches
    883         // the top of the screen when scrolling down.
    884         int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
    885         if (mIsScrollingUp) {
    886             child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
    887         } else if (offset != 0) {
    888             child = (WeekView) view.getChildAt(offset);
    889         }
    890 
    891         if (child != null) {
    892             // Find out which month we're moving into
    893             int month;
    894             if (mIsScrollingUp) {
    895                 month = child.getMonthOfFirstWeekDay();
    896             } else {
    897                 month = child.getMonthOfLastWeekDay();
    898             }
    899 
    900             // And how it relates to our current highlighted month
    901             int monthDiff;
    902             if (mCurrentMonthDisplayed == 11 && month == 0) {
    903                 monthDiff = 1;
    904             } else if (mCurrentMonthDisplayed == 0 && month == 11) {
    905                 monthDiff = -1;
    906             } else {
    907                 monthDiff = month - mCurrentMonthDisplayed;
    908             }
    909 
    910             // Only switch months if we're scrolling away from the currently
    911             // selected month
    912             if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
    913                 Calendar firstDay = child.getFirstDay();
    914                 if (mIsScrollingUp) {
    915                     firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
    916                 } else {
    917                     firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
    918                 }
    919                 setMonthDisplayed(firstDay);
    920             }
    921         }
    922         mPreviousScrollPosition = currScroll;
    923         mPreviousScrollState = mCurrentScrollState;
    924     }
    925 
    926     /**
    927      * Sets the month displayed at the top of this view based on time. Override
    928      * to add custom events when the title is changed.
    929      *
    930      * @param calendar A day in the new focus month.
    931      */
    932     private void setMonthDisplayed(Calendar calendar) {
    933         mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
    934         mAdapter.setFocusMonth(mCurrentMonthDisplayed);
    935         final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
    936                 | DateUtils.FORMAT_SHOW_YEAR;
    937         final long millis = calendar.getTimeInMillis();
    938         String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
    939         mMonthName.setText(newMonthName);
    940         mMonthName.invalidate();
    941     }
    942 
    943     /**
    944      * @return Returns the number of weeks between the current <code>date</code>
    945      *         and the <code>mMinDate</code>.
    946      */
    947     private int getWeeksSinceMinDate(Calendar date) {
    948         if (date.before(mMinDate)) {
    949             throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
    950                     + " does not precede toDate: " + date.getTime());
    951         }
    952         long endTimeMillis = date.getTimeInMillis()
    953                 + date.getTimeZone().getOffset(date.getTimeInMillis());
    954         long startTimeMillis = mMinDate.getTimeInMillis()
    955                 + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
    956         long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
    957                 * MILLIS_IN_DAY;
    958         return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
    959     }
    960 
    961     /**
    962      * Command responsible for acting upon scroll state changes.
    963      */
    964     private class ScrollStateRunnable implements Runnable {
    965         private AbsListView mView;
    966 
    967         private int mNewState;
    968 
    969         /**
    970          * Sets up the runnable with a short delay in case the scroll state
    971          * immediately changes again.
    972          *
    973          * @param view The list view that changed state
    974          * @param scrollState The new state it changed to
    975          */
    976         public void doScrollStateChange(AbsListView view, int scrollState) {
    977             mView = view;
    978             mNewState = scrollState;
    979             mDelegator.removeCallbacks(this);
    980             mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
    981         }
    982 
    983         public void run() {
    984             mCurrentScrollState = mNewState;
    985             // Fix the position after a scroll or a fling ends
    986             if (mNewState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE
    987                     && mPreviousScrollState != AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
    988                 View child = mView.getChildAt(0);
    989                 if (child == null) {
    990                     // The view is no longer visible, just return
    991                     return;
    992                 }
    993                 int dist = child.getBottom() - mListScrollTopOffset;
    994                 if (dist > mListScrollTopOffset) {
    995                     if (mIsScrollingUp) {
    996                         mView.smoothScrollBy(dist - child.getHeight(),
    997                                 ADJUSTMENT_SCROLL_DURATION);
    998                     } else {
    999                         mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
   1000                     }
   1001                 }
   1002             }
   1003             mPreviousScrollState = mNewState;
   1004         }
   1005     }
   1006 
   1007     /**
   1008      * <p>
   1009      * This is a specialized adapter for creating a list of weeks with
   1010      * selectable days. It can be configured to display the week number, start
   1011      * the week on a given day, show a reduced number of days, or display an
   1012      * arbitrary number of weeks at a time.
   1013      * </p>
   1014      */
   1015     private class WeeksAdapter extends BaseAdapter implements View.OnTouchListener {
   1016 
   1017         private int mSelectedWeek;
   1018 
   1019         private GestureDetector mGestureDetector;
   1020 
   1021         private int mFocusedMonth;
   1022 
   1023         private final Calendar mSelectedDate = Calendar.getInstance();
   1024 
   1025         private int mTotalWeekCount;
   1026 
   1027         public WeeksAdapter(Context context) {
   1028             mContext = context;
   1029             mGestureDetector = new GestureDetector(mContext, new WeeksAdapter.CalendarGestureListener());
   1030             init();
   1031         }
   1032 
   1033         /**
   1034          * Set up the gesture detector and selected time
   1035          */
   1036         private void init() {
   1037             mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
   1038             mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
   1039             if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
   1040                     || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
   1041                 mTotalWeekCount++;
   1042             }
   1043             notifyDataSetChanged();
   1044         }
   1045 
   1046         /**
   1047          * Updates the selected day and related parameters.
   1048          *
   1049          * @param selectedDay The time to highlight
   1050          */
   1051         public void setSelectedDay(Calendar selectedDay) {
   1052             if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
   1053                     && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
   1054                 return;
   1055             }
   1056             mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
   1057             mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
   1058             mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
   1059             notifyDataSetChanged();
   1060         }
   1061 
   1062         /**
   1063          * @return The selected day of month.
   1064          */
   1065         public Calendar getSelectedDay() {
   1066             return mSelectedDate;
   1067         }
   1068 
   1069         @Override
   1070         public int getCount() {
   1071             return mTotalWeekCount;
   1072         }
   1073 
   1074         @Override
   1075         public Object getItem(int position) {
   1076             return null;
   1077         }
   1078 
   1079         @Override
   1080         public long getItemId(int position) {
   1081             return position;
   1082         }
   1083 
   1084         @Override
   1085         public View getView(int position, View convertView, ViewGroup parent) {
   1086             WeekView weekView = null;
   1087             if (convertView != null) {
   1088                 weekView = (WeekView) convertView;
   1089             } else {
   1090                 weekView = new WeekView(mContext);
   1091                 AbsListView.LayoutParams params =
   1092                         new AbsListView.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
   1093                                 FrameLayout.LayoutParams.WRAP_CONTENT);
   1094                 weekView.setLayoutParams(params);
   1095                 weekView.setClickable(true);
   1096                 weekView.setOnTouchListener(this);
   1097             }
   1098 
   1099             int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
   1100                     Calendar.DAY_OF_WEEK) : -1;
   1101             weekView.init(position, selectedWeekDay, mFocusedMonth);
   1102 
   1103             return weekView;
   1104         }
   1105 
   1106         /**
   1107          * Changes which month is in focus and updates the view.
   1108          *
   1109          * @param month The month to show as in focus [0-11]
   1110          */
   1111         public void setFocusMonth(int month) {
   1112             if (mFocusedMonth == month) {
   1113                 return;
   1114             }
   1115             mFocusedMonth = month;
   1116             notifyDataSetChanged();
   1117         }
   1118 
   1119         @Override
   1120         public boolean onTouch(View v, MotionEvent event) {
   1121             if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
   1122                 WeekView weekView = (WeekView) v;
   1123                 // if we cannot find a day for the given location we are done
   1124                 if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
   1125                     return true;
   1126                 }
   1127                 // it is possible that the touched day is outside the valid range
   1128                 // we draw whole weeks but range end can fall not on the week end
   1129                 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
   1130                     return true;
   1131                 }
   1132                 onDateTapped(mTempDate);
   1133                 return true;
   1134             }
   1135             return false;
   1136         }
   1137 
   1138         /**
   1139          * Maintains the same hour/min/sec but moves the day to the tapped day.
   1140          *
   1141          * @param day The day that was tapped
   1142          */
   1143         private void onDateTapped(Calendar day) {
   1144             setSelectedDay(day);
   1145             setMonthDisplayed(day);
   1146         }
   1147 
   1148         /**
   1149          * This is here so we can identify single tap events and set the
   1150          * selected day correctly
   1151          */
   1152         class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
   1153             @Override
   1154             public boolean onSingleTapUp(MotionEvent e) {
   1155                 return true;
   1156             }
   1157         }
   1158     }
   1159 
   1160     /**
   1161      * <p>
   1162      * This is a dynamic view for drawing a single week. It can be configured to
   1163      * display the week number, start the week on a given day, or show a reduced
   1164      * number of days. It is intended for use as a single view within a
   1165      * ListView. See {@link WeeksAdapter} for usage.
   1166      * </p>
   1167      */
   1168     private class WeekView extends View {
   1169 
   1170         private final Rect mTempRect = new Rect();
   1171 
   1172         private final Paint mDrawPaint = new Paint();
   1173 
   1174         private final Paint mMonthNumDrawPaint = new Paint();
   1175 
   1176         // Cache the number strings so we don't have to recompute them each time
   1177         private String[] mDayNumbers;
   1178 
   1179         // Quick lookup for checking which days are in the focus month
   1180         private boolean[] mFocusDay;
   1181 
   1182         // Whether this view has a focused day.
   1183         private boolean mHasFocusedDay;
   1184 
   1185         // Whether this view has only focused days.
   1186         private boolean mHasUnfocusedDay;
   1187 
   1188         // The first day displayed by this item
   1189         private Calendar mFirstDay;
   1190 
   1191         // The month of the first day in this week
   1192         private int mMonthOfFirstWeekDay = -1;
   1193 
   1194         // The month of the last day in this week
   1195         private int mLastWeekDayMonth = -1;
   1196 
   1197         // The position of this week, equivalent to weeks since the week of Jan
   1198         // 1st, 1900
   1199         private int mWeek = -1;
   1200 
   1201         // Quick reference to the width of this view, matches parent
   1202         private int mWidth;
   1203 
   1204         // The height this view should draw at in pixels, set by height param
   1205         private int mHeight;
   1206 
   1207         // If this view contains the selected day
   1208         private boolean mHasSelectedDay = false;
   1209 
   1210         // Which day is selected [0-6] or -1 if no day is selected
   1211         private int mSelectedDay = -1;
   1212 
   1213         // The number of days + a spot for week number if it is displayed
   1214         private int mNumCells;
   1215 
   1216         // The left edge of the selected day
   1217         private int mSelectedLeft = -1;
   1218 
   1219         // The right edge of the selected day
   1220         private int mSelectedRight = -1;
   1221 
   1222         public WeekView(Context context) {
   1223             super(context);
   1224 
   1225             // Sets up any standard paints that will be used
   1226             initializePaints();
   1227         }
   1228 
   1229         /**
   1230          * Initializes this week view.
   1231          *
   1232          * @param weekNumber The number of the week this view represents. The
   1233          *            week number is a zero based index of the weeks since
   1234          *            {@link android.widget.CalendarView#getMinDate()}.
   1235          * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
   1236          *            selected day.
   1237          * @param focusedMonth The month that is currently in focus i.e.
   1238          *            highlighted.
   1239          */
   1240         public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
   1241             mSelectedDay = selectedWeekDay;
   1242             mHasSelectedDay = mSelectedDay != -1;
   1243             mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
   1244             mWeek = weekNumber;
   1245             mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
   1246 
   1247             mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
   1248             mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
   1249 
   1250             // Allocate space for caching the day numbers and focus values
   1251             mDayNumbers = new String[mNumCells];
   1252             mFocusDay = new boolean[mNumCells];
   1253 
   1254             // If we're showing the week number calculate it based on Monday
   1255             int i = 0;
   1256             if (mShowWeekNumber) {
   1257                 mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
   1258                         mTempDate.get(Calendar.WEEK_OF_YEAR));
   1259                 i++;
   1260             }
   1261 
   1262             // Now adjust our starting day based on the start day of the week
   1263             int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
   1264             mTempDate.add(Calendar.DAY_OF_MONTH, diff);
   1265 
   1266             mFirstDay = (Calendar) mTempDate.clone();
   1267             mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
   1268 
   1269             mHasUnfocusedDay = true;
   1270             for (; i < mNumCells; i++) {
   1271                 final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
   1272                 mFocusDay[i] = isFocusedDay;
   1273                 mHasFocusedDay |= isFocusedDay;
   1274                 mHasUnfocusedDay &= !isFocusedDay;
   1275                 // do not draw dates outside the valid range to avoid user confusion
   1276                 if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
   1277                     mDayNumbers[i] = "";
   1278                 } else {
   1279                     mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
   1280                             mTempDate.get(Calendar.DAY_OF_MONTH));
   1281                 }
   1282                 mTempDate.add(Calendar.DAY_OF_MONTH, 1);
   1283             }
   1284             // We do one extra add at the end of the loop, if that pushed us to
   1285             // new month undo it
   1286             if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
   1287                 mTempDate.add(Calendar.DAY_OF_MONTH, -1);
   1288             }
   1289             mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
   1290 
   1291             updateSelectionPositions();
   1292         }
   1293 
   1294         /**
   1295          * Initialize the paint instances.
   1296          */
   1297         private void initializePaints() {
   1298             mDrawPaint.setFakeBoldText(false);
   1299             mDrawPaint.setAntiAlias(true);
   1300             mDrawPaint.setStyle(Paint.Style.FILL);
   1301 
   1302             mMonthNumDrawPaint.setFakeBoldText(true);
   1303             mMonthNumDrawPaint.setAntiAlias(true);
   1304             mMonthNumDrawPaint.setStyle(Paint.Style.FILL);
   1305             mMonthNumDrawPaint.setTextAlign(Paint.Align.CENTER);
   1306             mMonthNumDrawPaint.setTextSize(mDateTextSize);
   1307         }
   1308 
   1309         /**
   1310          * Returns the month of the first day in this week.
   1311          *
   1312          * @return The month the first day of this view is in.
   1313          */
   1314         public int getMonthOfFirstWeekDay() {
   1315             return mMonthOfFirstWeekDay;
   1316         }
   1317 
   1318         /**
   1319          * Returns the month of the last day in this week
   1320          *
   1321          * @return The month the last day of this view is in
   1322          */
   1323         public int getMonthOfLastWeekDay() {
   1324             return mLastWeekDayMonth;
   1325         }
   1326 
   1327         /**
   1328          * Returns the first day in this view.
   1329          *
   1330          * @return The first day in the view.
   1331          */
   1332         public Calendar getFirstDay() {
   1333             return mFirstDay;
   1334         }
   1335 
   1336         /**
   1337          * Calculates the day that the given x position is in, accounting for
   1338          * week number.
   1339          *
   1340          * @param x The x position of the touch event.
   1341          * @return True if a day was found for the given location.
   1342          */
   1343         public boolean getDayFromLocation(float x, Calendar outCalendar) {
   1344             final boolean isLayoutRtl = isLayoutRtl();
   1345 
   1346             int start;
   1347             int end;
   1348 
   1349             if (isLayoutRtl) {
   1350                 start = 0;
   1351                 end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
   1352             } else {
   1353                 start = mShowWeekNumber ? mWidth / mNumCells : 0;
   1354                 end = mWidth;
   1355             }
   1356 
   1357             if (x < start || x > end) {
   1358                 outCalendar.clear();
   1359                 return false;
   1360             }
   1361 
   1362             // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
   1363             int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
   1364 
   1365             if (isLayoutRtl) {
   1366                 dayPosition = mDaysPerWeek - 1 - dayPosition;
   1367             }
   1368 
   1369             outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
   1370             outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
   1371 
   1372             return true;
   1373         }
   1374 
   1375         public boolean getBoundsForDate(Calendar date, Rect outBounds) {
   1376             Calendar currDay = Calendar.getInstance();
   1377             currDay.setTime(mFirstDay.getTime());
   1378             for (int i = 0; i < mDaysPerWeek; i++) {
   1379                 if ((date.get(Calendar.YEAR) == currDay.get(Calendar.YEAR))
   1380                     && (date.get(Calendar.MONTH) == currDay.get(Calendar.MONTH))
   1381                     && (date.get(Calendar.DAY_OF_MONTH) == currDay.get(Calendar.DAY_OF_MONTH))) {
   1382                     // We found the matching date. Follow the logic in the draw pass that divides
   1383                     // the available horizontal space equally between all the entries in this week.
   1384                     // Note that if we're showing week number, the start entry will be that number.
   1385                     int cellSize = mWidth / mNumCells;
   1386                     if (isLayoutRtl()) {
   1387                         outBounds.left = cellSize *
   1388                                 (mShowWeekNumber ? (mNumCells - i - 2) : (mNumCells - i - 1));
   1389                     } else {
   1390                         outBounds.left = cellSize * (mShowWeekNumber ? i + 1 : i);
   1391                     }
   1392                     outBounds.top = 0;
   1393                     outBounds.right = outBounds.left + cellSize;
   1394                     outBounds.bottom = getHeight();
   1395                     return true;
   1396                 }
   1397                 // Add one day
   1398                 currDay.add(Calendar.DAY_OF_MONTH, 1);
   1399             }
   1400             return false;
   1401         }
   1402 
   1403         @Override
   1404         protected void onDraw(Canvas canvas) {
   1405             drawBackground(canvas);
   1406             drawWeekNumbersAndDates(canvas);
   1407             drawWeekSeparators(canvas);
   1408             drawSelectedDateVerticalBars(canvas);
   1409         }
   1410 
   1411         /**
   1412          * This draws the selection highlight if a day is selected in this week.
   1413          *
   1414          * @param canvas The canvas to draw on
   1415          */
   1416         private void drawBackground(Canvas canvas) {
   1417             if (!mHasSelectedDay) {
   1418                 return;
   1419             }
   1420             mDrawPaint.setColor(mSelectedWeekBackgroundColor);
   1421 
   1422             mTempRect.top = mWeekSeparatorLineWidth;
   1423             mTempRect.bottom = mHeight;
   1424 
   1425             final boolean isLayoutRtl = isLayoutRtl();
   1426 
   1427             if (isLayoutRtl) {
   1428                 mTempRect.left = 0;
   1429                 mTempRect.right = mSelectedLeft - 2;
   1430             } else {
   1431                 mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
   1432                 mTempRect.right = mSelectedLeft - 2;
   1433             }
   1434             canvas.drawRect(mTempRect, mDrawPaint);
   1435 
   1436             if (isLayoutRtl) {
   1437                 mTempRect.left = mSelectedRight + 3;
   1438                 mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
   1439             } else {
   1440                 mTempRect.left = mSelectedRight + 3;
   1441                 mTempRect.right = mWidth;
   1442             }
   1443             canvas.drawRect(mTempRect, mDrawPaint);
   1444         }
   1445 
   1446         /**
   1447          * Draws the week and month day numbers for this week.
   1448          *
   1449          * @param canvas The canvas to draw on
   1450          */
   1451         private void drawWeekNumbersAndDates(Canvas canvas) {
   1452             final float textHeight = mDrawPaint.getTextSize();
   1453             final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeparatorLineWidth;
   1454             final int nDays = mNumCells;
   1455             final int divisor = 2 * nDays;
   1456 
   1457             mDrawPaint.setTextAlign(Paint.Align.CENTER);
   1458             mDrawPaint.setTextSize(mDateTextSize);
   1459 
   1460             int i = 0;
   1461 
   1462             if (isLayoutRtl()) {
   1463                 for (; i < nDays - 1; i++) {
   1464                     mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
   1465                             : mUnfocusedMonthDateColor);
   1466                     int x = (2 * i + 1) * mWidth / divisor;
   1467                     canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
   1468                 }
   1469                 if (mShowWeekNumber) {
   1470                     mDrawPaint.setColor(mWeekNumberColor);
   1471                     int x = mWidth - mWidth / divisor;
   1472                     canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
   1473                 }
   1474             } else {
   1475                 if (mShowWeekNumber) {
   1476                     mDrawPaint.setColor(mWeekNumberColor);
   1477                     int x = mWidth / divisor;
   1478                     canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
   1479                     i++;
   1480                 }
   1481                 for (; i < nDays; i++) {
   1482                     mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
   1483                             : mUnfocusedMonthDateColor);
   1484                     int x = (2 * i + 1) * mWidth / divisor;
   1485                     canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
   1486                 }
   1487             }
   1488         }
   1489 
   1490         /**
   1491          * Draws a horizontal line for separating the weeks.
   1492          *
   1493          * @param canvas The canvas to draw on.
   1494          */
   1495         private void drawWeekSeparators(Canvas canvas) {
   1496             // If it is the topmost fully visible child do not draw separator line
   1497             int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
   1498             if (mListView.getChildAt(0).getTop() < 0) {
   1499                 firstFullyVisiblePosition++;
   1500             }
   1501             if (firstFullyVisiblePosition == mWeek) {
   1502                 return;
   1503             }
   1504             mDrawPaint.setColor(mWeekSeparatorLineColor);
   1505             mDrawPaint.setStrokeWidth(mWeekSeparatorLineWidth);
   1506             float startX;
   1507             float stopX;
   1508             if (isLayoutRtl()) {
   1509                 startX = 0;
   1510                 stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
   1511             } else {
   1512                 startX = mShowWeekNumber ? mWidth / mNumCells : 0;
   1513                 stopX = mWidth;
   1514             }
   1515             canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
   1516         }
   1517 
   1518         /**
   1519          * Draws the selected date bars if this week has a selected day.
   1520          *
   1521          * @param canvas The canvas to draw on
   1522          */
   1523         private void drawSelectedDateVerticalBars(Canvas canvas) {
   1524             if (!mHasSelectedDay) {
   1525                 return;
   1526             }
   1527             mSelectedDateVerticalBar.setBounds(
   1528                     mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
   1529                     mWeekSeparatorLineWidth,
   1530                     mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
   1531                     mHeight);
   1532             mSelectedDateVerticalBar.draw(canvas);
   1533             mSelectedDateVerticalBar.setBounds(
   1534                     mSelectedRight - mSelectedDateVerticalBarWidth / 2,
   1535                     mWeekSeparatorLineWidth,
   1536                     mSelectedRight + mSelectedDateVerticalBarWidth / 2,
   1537                     mHeight);
   1538             mSelectedDateVerticalBar.draw(canvas);
   1539         }
   1540 
   1541         @Override
   1542         protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   1543             mWidth = w;
   1544             updateSelectionPositions();
   1545         }
   1546 
   1547         /**
   1548          * This calculates the positions for the selected day lines.
   1549          */
   1550         private void updateSelectionPositions() {
   1551             if (mHasSelectedDay) {
   1552                 final boolean isLayoutRtl = isLayoutRtl();
   1553                 int selectedPosition = mSelectedDay - mFirstDayOfWeek;
   1554                 if (selectedPosition < 0) {
   1555                     selectedPosition += 7;
   1556                 }
   1557                 if (mShowWeekNumber && !isLayoutRtl) {
   1558                     selectedPosition++;
   1559                 }
   1560                 if (isLayoutRtl) {
   1561                     mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
   1562 
   1563                 } else {
   1564                     mSelectedLeft = selectedPosition * mWidth / mNumCells;
   1565                 }
   1566                 mSelectedRight = mSelectedLeft + mWidth / mNumCells;
   1567             }
   1568         }
   1569 
   1570         @Override
   1571         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1572             mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
   1573                     .getPaddingBottom()) / mShownWeekCount;
   1574             setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
   1575         }
   1576     }
   1577 
   1578 }
   1579