SimpleMonthView.java revision c5b95c20b6fd3f4e63147efb22dd19c657b17001
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 17package android.widget; 18 19import android.content.Context; 20import android.content.res.ColorStateList; 21import android.content.res.Configuration; 22import android.content.res.Resources; 23import android.graphics.Canvas; 24import android.graphics.Paint; 25import android.graphics.Paint.Align; 26import android.graphics.Paint.Style; 27import android.graphics.Rect; 28import android.graphics.Typeface; 29import android.os.Bundle; 30import android.text.format.DateFormat; 31import android.text.format.DateUtils; 32import android.text.format.Time; 33import android.util.AttributeSet; 34import android.util.IntArray; 35import android.util.StateSet; 36import android.view.MotionEvent; 37import android.view.View; 38import android.view.accessibility.AccessibilityEvent; 39import android.view.accessibility.AccessibilityNodeInfo; 40 41import com.android.internal.R; 42import com.android.internal.widget.ExploreByTouchHelper; 43 44import java.text.SimpleDateFormat; 45import java.util.Calendar; 46import java.util.Formatter; 47import java.util.Locale; 48 49/** 50 * A calendar-like view displaying a specified month and the appropriate selectable day numbers 51 * within the specified month. 52 */ 53class SimpleMonthView extends View { 54 private static final int MIN_ROW_HEIGHT = 10; 55 56 private static final int DEFAULT_SELECTED_DAY = -1; 57 private static final int DEFAULT_WEEK_START = Calendar.SUNDAY; 58 private static final int DEFAULT_NUM_DAYS = 7; 59 private static final int DEFAULT_NUM_ROWS = 6; 60 private static final int MAX_NUM_ROWS = 6; 61 62 private static final int DAY_SEPARATOR_WIDTH = 1; 63 64 private final Formatter mFormatter; 65 private final StringBuilder mStringBuilder; 66 67 private final int mMonthTextSize; 68 private final int mDayOfWeekTextSize; 69 private final int mDayTextSize; 70 71 private final int mMonthHeaderHeight; 72 73 private final Paint mMonthPaint = new Paint(); 74 private final Paint mDayOfWeekPaint = new Paint(); 75 private final Paint mDayPaint = new Paint(); 76 private final Paint mDayBackgroundPaint = new Paint(); 77 78 /** Single-letter (when available) formatter for the day of week label. */ 79 private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault()); 80 81 // affects the padding on the sides of this view 82 private int mPadding = 0; 83 84 private String mDayOfWeekTypeface; 85 private String mMonthTypeface; 86 87 private int mMonth; 88 private int mYear; 89 90 // Quick reference to the width of this view, matches parent 91 private int mWidth; 92 93 // The height this view should draw at in pixels, set by height param 94 private final int mRowHeight; 95 96 // If this view contains the today 97 private boolean mHasToday = false; 98 99 // Which day is selected [0-6] or -1 if no day is selected 100 private int mActivatedDay = -1; 101 102 // Which day is today [0-6] or -1 if no day is today 103 private int mToday = DEFAULT_SELECTED_DAY; 104 105 // Which day of the week to start on [0-6] 106 private int mWeekStart = DEFAULT_WEEK_START; 107 108 // How many days to display 109 private int mNumDays = DEFAULT_NUM_DAYS; 110 111 // The number of days + a spot for week number if it is displayed 112 private int mNumCells = mNumDays; 113 114 private int mDayOfWeekStart = 0; 115 116 // First enabled day 117 private int mEnabledDayStart = 1; 118 119 // Last enabled day 120 private int mEnabledDayEnd = 31; 121 122 private final Calendar mCalendar = Calendar.getInstance(); 123 private final Calendar mDayLabelCalendar = Calendar.getInstance(); 124 125 private final MonthViewTouchHelper mTouchHelper; 126 127 private int mNumRows = DEFAULT_NUM_ROWS; 128 129 // Optional listener for handling day click actions 130 private OnDayClickListener mOnDayClickListener; 131 132 // Whether to prevent setting the accessibility delegate 133 private boolean mLockAccessibilityDelegate; 134 135 private int mNormalTextColor; 136 private int mDisabledTextColor; 137 private int mSelectedDayColor; 138 139 private ColorStateList mDayTextColor; 140 141 public SimpleMonthView(Context context) { 142 this(context, null); 143 } 144 145 public SimpleMonthView(Context context, AttributeSet attrs) { 146 this(context, attrs, R.attr.datePickerStyle); 147 } 148 149 public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr) { 150 this(context, attrs, defStyleAttr, 0); 151 } 152 153 public SimpleMonthView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 154 super(context, attrs, defStyleAttr, defStyleRes); 155 156 final Resources res = context.getResources(); 157 mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface); 158 mMonthTypeface = res.getString(R.string.sans_serif); 159 160 mStringBuilder = new StringBuilder(50); 161 mFormatter = new Formatter(mStringBuilder, Locale.getDefault()); 162 163 mDayTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size); 164 mMonthTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size); 165 mDayOfWeekTextSize = res.getDimensionPixelSize( 166 R.dimen.datepicker_month_day_label_text_size); 167 mMonthHeaderHeight = res.getDimensionPixelOffset( 168 R.dimen.datepicker_month_list_item_header_height); 169 170 mRowHeight = Math.max(MIN_ROW_HEIGHT, 171 (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height) 172 - mMonthHeaderHeight) / MAX_NUM_ROWS); 173 174 // Set up accessibility components. 175 mTouchHelper = new MonthViewTouchHelper(this); 176 setAccessibilityDelegate(mTouchHelper); 177 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 178 mLockAccessibilityDelegate = true; 179 180 initPaints(); 181 } 182 183 /** 184 * Sets up the text and style properties for painting. 185 */ 186 private void initPaints() { 187 mMonthPaint.setAntiAlias(true); 188 mMonthPaint.setTextSize(mMonthTextSize); 189 mMonthPaint.setTypeface(Typeface.create(mMonthTypeface, Typeface.BOLD)); 190 mMonthPaint.setTextAlign(Align.CENTER); 191 mMonthPaint.setStyle(Style.FILL); 192 193 mDayOfWeekPaint.setAntiAlias(true); 194 mDayOfWeekPaint.setTextSize(mDayOfWeekTextSize); 195 mDayOfWeekPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.BOLD)); 196 mDayOfWeekPaint.setTextAlign(Align.CENTER); 197 mDayOfWeekPaint.setStyle(Style.FILL); 198 199 mDayBackgroundPaint.setAntiAlias(true); 200 mDayBackgroundPaint.setStyle(Style.FILL); 201 202 mDayPaint.setAntiAlias(true); 203 mDayPaint.setTextSize(mDayTextSize); 204 mDayPaint.setTextAlign(Align.CENTER); 205 mDayPaint.setStyle(Style.FILL); 206 } 207 208 @Override 209 protected void onConfigurationChanged(Configuration newConfig) { 210 super.onConfigurationChanged(newConfig); 211 212 mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale); 213 } 214 215 void setMonthTextColor(ColorStateList monthTextColor) { 216 final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0); 217 mMonthPaint.setColor(enabledColor); 218 invalidate(); 219 } 220 221 void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) { 222 final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0); 223 mDayOfWeekPaint.setColor(enabledColor); 224 invalidate(); 225 } 226 227 void setDayTextColor(ColorStateList dayTextColor) { 228 mDayTextColor = dayTextColor; 229 invalidate(); 230 } 231 232 void setDayBackgroundColor(ColorStateList daySelectorColor) { 233 final int activatedColor = daySelectorColor.getColorForState( 234 StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0); 235 mDayBackgroundPaint.setColor(activatedColor); 236 invalidate(); 237 } 238 239 @Override 240 public void setAccessibilityDelegate(AccessibilityDelegate delegate) { 241 // Workaround for a JB MR1 issue where accessibility delegates on 242 // top-level ListView items are overwritten. 243 if (!mLockAccessibilityDelegate) { 244 super.setAccessibilityDelegate(delegate); 245 } 246 } 247 248 public void setOnDayClickListener(OnDayClickListener listener) { 249 mOnDayClickListener = listener; 250 } 251 252 @Override 253 public boolean dispatchHoverEvent(MotionEvent event) { 254 // First right-of-refusal goes the touch exploration helper. 255 if (mTouchHelper.dispatchHoverEvent(event)) { 256 return true; 257 } 258 return super.dispatchHoverEvent(event); 259 } 260 261 @Override 262 public boolean onTouchEvent(MotionEvent event) { 263 switch (event.getAction()) { 264 case MotionEvent.ACTION_UP: 265 final int day = getDayFromLocation(event.getX(), event.getY()); 266 if (day >= 0) { 267 onDayClick(day); 268 } 269 break; 270 } 271 return true; 272 } 273 274 @Override 275 protected void onDraw(Canvas canvas) { 276 drawMonthTitle(canvas); 277 drawWeekDayLabels(canvas); 278 drawDays(canvas); 279 } 280 281 private static boolean isValidDayOfWeek(int day) { 282 return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY; 283 } 284 285 private static boolean isValidMonth(int month) { 286 return month >= Calendar.JANUARY && month <= Calendar.DECEMBER; 287 } 288 289 /** 290 * Sets all the parameters for displaying this week. Parameters have a default value and 291 * will only update if a new value is included, except for focus month, which will always 292 * default to no focus month if no value is passed in. The only required parameter is the 293 * week start. 294 * 295 * @param selectedDay the selected day of the month, or -1 for no selection. 296 * @param month the month. 297 * @param year the year. 298 * @param weekStart which day the week should start on. {@link Calendar#SUNDAY} through 299 * {@link Calendar#SATURDAY}. 300 * @param enabledDayStart the first enabled day. 301 * @param enabledDayEnd the last enabled day. 302 */ 303 void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart, 304 int enabledDayEnd) { 305 mActivatedDay = selectedDay; 306 307 if (isValidMonth(month)) { 308 mMonth = month; 309 } 310 mYear = year; 311 312 // Figure out what day today is 313 final Time today = new Time(Time.getCurrentTimezone()); 314 today.setToNow(); 315 mHasToday = false; 316 mToday = -1; 317 318 mCalendar.set(Calendar.MONTH, mMonth); 319 mCalendar.set(Calendar.YEAR, mYear); 320 mCalendar.set(Calendar.DAY_OF_MONTH, 1); 321 mDayOfWeekStart = mCalendar.get(Calendar.DAY_OF_WEEK); 322 323 if (isValidDayOfWeek(weekStart)) { 324 mWeekStart = weekStart; 325 } else { 326 mWeekStart = mCalendar.getFirstDayOfWeek(); 327 } 328 329 if (enabledDayStart > 0 && enabledDayEnd < 32) { 330 mEnabledDayStart = enabledDayStart; 331 } 332 if (enabledDayEnd > 0 && enabledDayEnd < 32 && enabledDayEnd >= enabledDayStart) { 333 mEnabledDayEnd = enabledDayEnd; 334 } 335 336 mNumCells = getDaysInMonth(mMonth, mYear); 337 for (int i = 0; i < mNumCells; i++) { 338 final int day = i + 1; 339 if (sameDay(day, today)) { 340 mHasToday = true; 341 mToday = day; 342 } 343 } 344 mNumRows = calculateNumRows(); 345 346 // Invalidate cached accessibility information. 347 mTouchHelper.invalidateRoot(); 348 } 349 350 private static int getDaysInMonth(int month, int year) { 351 switch (month) { 352 case Calendar.JANUARY: 353 case Calendar.MARCH: 354 case Calendar.MAY: 355 case Calendar.JULY: 356 case Calendar.AUGUST: 357 case Calendar.OCTOBER: 358 case Calendar.DECEMBER: 359 return 31; 360 case Calendar.APRIL: 361 case Calendar.JUNE: 362 case Calendar.SEPTEMBER: 363 case Calendar.NOVEMBER: 364 return 30; 365 case Calendar.FEBRUARY: 366 return (year % 4 == 0) ? 29 : 28; 367 default: 368 throw new IllegalArgumentException("Invalid Month"); 369 } 370 } 371 372 public void reuse() { 373 mNumRows = DEFAULT_NUM_ROWS; 374 requestLayout(); 375 } 376 377 private int calculateNumRows() { 378 int offset = findDayOffset(); 379 int dividend = (offset + mNumCells) / mNumDays; 380 int remainder = (offset + mNumCells) % mNumDays; 381 return (dividend + (remainder > 0 ? 1 : 0)); 382 } 383 384 private boolean sameDay(int day, Time today) { 385 return mYear == today.year && 386 mMonth == today.month && 387 day == today.monthDay; 388 } 389 390 @Override 391 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 392 setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows 393 + mMonthHeaderHeight); 394 } 395 396 @Override 397 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 398 mWidth = w; 399 400 // Invalidate cached accessibility information. 401 mTouchHelper.invalidateRoot(); 402 } 403 404 private String getMonthAndYearString() { 405 int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR 406 | DateUtils.FORMAT_NO_MONTH_DAY; 407 mStringBuilder.setLength(0); 408 long millis = mCalendar.getTimeInMillis(); 409 return DateUtils.formatDateRange(getContext(), mFormatter, millis, millis, flags, 410 Time.getCurrentTimezone()).toString(); 411 } 412 413 private void drawMonthTitle(Canvas canvas) { 414 final float x = (mWidth + 2 * mPadding) / 2f; 415 final float y = (mMonthHeaderHeight - mDayOfWeekTextSize) / 2f; 416 canvas.drawText(getMonthAndYearString(), x, y, mMonthPaint); 417 } 418 419 private void drawWeekDayLabels(Canvas canvas) { 420 final int y = mMonthHeaderHeight - (mDayOfWeekTextSize / 2); 421 final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); 422 423 for (int i = 0; i < mNumDays; i++) { 424 final int calendarDay = (i + mWeekStart) % mNumDays; 425 mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay); 426 427 final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime()); 428 final int x = (2 * i + 1) * dayWidthHalf + mPadding; 429 canvas.drawText(dayLabel, x, y, mDayOfWeekPaint); 430 } 431 } 432 433 /** 434 * Draws the month days. 435 */ 436 private void drawDays(Canvas canvas) { 437 final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2); 438 int y = (((mRowHeight + mDayTextSize) / 2) - DAY_SEPARATOR_WIDTH) 439 + mMonthHeaderHeight; 440 int j = findDayOffset(); 441 442 for (int day = 1; day <= mNumCells; day++) { 443 final int x = (2 * j + 1) * dayWidthHalf + mPadding; 444 int stateMask = 0; 445 446 if (day >= mEnabledDayStart && day <= mEnabledDayEnd) { 447 stateMask |= StateSet.VIEW_STATE_ENABLED; 448 } 449 450 if (mActivatedDay == day) { 451 stateMask |= StateSet.VIEW_STATE_ACTIVATED; 452 453 canvas.drawCircle(x, y - (mDayTextSize / 3), mRowHeight / 2, 454 mDayBackgroundPaint); 455 } 456 457 final int[] stateSet = StateSet.get(stateMask); 458 final int dayTextColor = mDayTextColor.getColorForState(stateSet, 0); 459 mDayPaint.setColor(dayTextColor); 460 461 final boolean isDayToday = mHasToday && mToday == day; 462 mDayPaint.setFakeBoldText(isDayToday); 463 464 canvas.drawText(String.format("%d", day), x, y, mDayPaint); 465 466 j++; 467 468 if (j == mNumDays) { 469 j = 0; 470 y += mRowHeight; 471 } 472 } 473 } 474 475 private int findDayOffset() { 476 return (mDayOfWeekStart < mWeekStart ? (mDayOfWeekStart + mNumDays) : mDayOfWeekStart) 477 - mWeekStart; 478 } 479 480 /** 481 * Calculates the day that the given x position is in, accounting for week 482 * number. Returns the day or -1 if the position wasn't in a day. 483 * 484 * @param x The x position of the touch event 485 * @return The day number, or -1 if the position wasn't in a day 486 */ 487 private int getDayFromLocation(float x, float y) { 488 int dayStart = mPadding; 489 if (x < dayStart || x > mWidth - mPadding) { 490 return -1; 491 } 492 // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels 493 int row = (int) (y - mMonthHeaderHeight) / mRowHeight; 494 int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding)); 495 496 int day = column - findDayOffset() + 1; 497 day += row * mNumDays; 498 if (day < 1 || day > mNumCells) { 499 return -1; 500 } 501 return day; 502 } 503 504 /** 505 * Called when the user clicks on a day. Handles callbacks to the 506 * {@link OnDayClickListener} if one is set. 507 * 508 * @param day The day that was clicked 509 */ 510 private void onDayClick(int day) { 511 if (mOnDayClickListener != null) { 512 Calendar date = Calendar.getInstance(); 513 date.set(mYear, mMonth, day); 514 mOnDayClickListener.onDayClick(this, date); 515 } 516 517 // This is a no-op if accessibility is turned off. 518 mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED); 519 } 520 521 /** 522 * @return The date that has accessibility focus, or {@code null} if no date 523 * has focus 524 */ 525 Calendar getAccessibilityFocus() { 526 final int day = mTouchHelper.getFocusedVirtualView(); 527 Calendar date = null; 528 if (day >= 0) { 529 date = Calendar.getInstance(); 530 date.set(mYear, mMonth, day); 531 } 532 return date; 533 } 534 535 /** 536 * Clears accessibility focus within the view. No-op if the view does not 537 * contain accessibility focus. 538 */ 539 public void clearAccessibilityFocus() { 540 mTouchHelper.clearFocusedVirtualView(); 541 } 542 543 /** 544 * Attempts to restore accessibility focus to the specified date. 545 * 546 * @param day The date which should receive focus 547 * @return {@code false} if the date is not valid for this month view, or 548 * {@code true} if the date received focus 549 */ 550 boolean restoreAccessibilityFocus(Calendar day) { 551 if ((day.get(Calendar.YEAR) != mYear) || (day.get(Calendar.MONTH) != mMonth) || 552 (day.get(Calendar.DAY_OF_MONTH) > mNumCells)) { 553 return false; 554 } 555 mTouchHelper.setFocusedVirtualView(day.get(Calendar.DAY_OF_MONTH)); 556 return true; 557 } 558 559 /** 560 * Provides a virtual view hierarchy for interfacing with an accessibility 561 * service. 562 */ 563 private class MonthViewTouchHelper extends ExploreByTouchHelper { 564 private static final String DATE_FORMAT = "dd MMMM yyyy"; 565 566 private final Rect mTempRect = new Rect(); 567 private final Calendar mTempCalendar = Calendar.getInstance(); 568 569 public MonthViewTouchHelper(View host) { 570 super(host); 571 } 572 573 public void setFocusedVirtualView(int virtualViewId) { 574 getAccessibilityNodeProvider(SimpleMonthView.this).performAction( 575 virtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); 576 } 577 578 public void clearFocusedVirtualView() { 579 final int focusedVirtualView = getFocusedVirtualView(); 580 if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) { 581 getAccessibilityNodeProvider(SimpleMonthView.this).performAction( 582 focusedVirtualView, 583 AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, 584 null); 585 } 586 } 587 588 @Override 589 protected int getVirtualViewAt(float x, float y) { 590 final int day = getDayFromLocation(x, y); 591 if (day >= 0) { 592 return day; 593 } 594 return ExploreByTouchHelper.INVALID_ID; 595 } 596 597 @Override 598 protected void getVisibleVirtualViews(IntArray virtualViewIds) { 599 for (int day = 1; day <= mNumCells; day++) { 600 virtualViewIds.add(day); 601 } 602 } 603 604 @Override 605 protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { 606 event.setContentDescription(getItemDescription(virtualViewId)); 607 } 608 609 @Override 610 protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { 611 getItemBounds(virtualViewId, mTempRect); 612 613 node.setContentDescription(getItemDescription(virtualViewId)); 614 node.setBoundsInParent(mTempRect); 615 node.addAction(AccessibilityNodeInfo.ACTION_CLICK); 616 617 if (virtualViewId == mActivatedDay) { 618 node.setSelected(true); 619 } 620 621 } 622 623 @Override 624 protected boolean onPerformActionForVirtualView(int virtualViewId, int action, 625 Bundle arguments) { 626 switch (action) { 627 case AccessibilityNodeInfo.ACTION_CLICK: 628 onDayClick(virtualViewId); 629 return true; 630 } 631 632 return false; 633 } 634 635 /** 636 * Calculates the bounding rectangle of a given time object. 637 * 638 * @param day The day to calculate bounds for 639 * @param rect The rectangle in which to store the bounds 640 */ 641 private void getItemBounds(int day, Rect rect) { 642 final int offsetX = mPadding; 643 final int offsetY = mMonthHeaderHeight; 644 final int cellHeight = mRowHeight; 645 final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays); 646 final int index = ((day - 1) + findDayOffset()); 647 final int row = (index / mNumDays); 648 final int column = (index % mNumDays); 649 final int x = (offsetX + (column * cellWidth)); 650 final int y = (offsetY + (row * cellHeight)); 651 652 rect.set(x, y, (x + cellWidth), (y + cellHeight)); 653 } 654 655 /** 656 * Generates a description for a given time object. Since this 657 * description will be spoken, the components are ordered by descending 658 * specificity as DAY MONTH YEAR. 659 * 660 * @param day The day to generate a description for 661 * @return A description of the time object 662 */ 663 private CharSequence getItemDescription(int day) { 664 mTempCalendar.set(mYear, mMonth, day); 665 final CharSequence date = DateFormat.format(DATE_FORMAT, 666 mTempCalendar.getTimeInMillis()); 667 668 if (day == mActivatedDay) { 669 return getContext().getString(R.string.item_is_selected, date); 670 } 671 672 return date; 673 } 674 } 675 676 /** 677 * Handles callbacks when the user clicks on a time object. 678 */ 679 public interface OnDayClickListener { 680 public void onDayClick(SimpleMonthView view, Calendar day); 681 } 682} 683