2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.calendar;
19 import static android.provider.Calendar.EVENT_BEGIN_TIME;
20 import static android.provider.Calendar.EVENT_END_TIME;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.graphics.Bitmap;
28 import android.graphics.Canvas;
29 import android.graphics.Paint;
30 import android.graphics.PorterDuff;
31 import android.graphics.Rect;
32 import android.graphics.drawable.Drawable;
33 import android.os.Handler;
34 import android.os.SystemClock;
35 import android.text.format.DateFormat;
36 import android.text.format.DateUtils;
37 import android.text.format.Time;
38 import android.util.Log;
39 import android.util.SparseArray;
40 import android.view.ContextMenu;
41 import android.view.GestureDetector;
42 import android.view.Gravity;
43 import android.view.KeyEvent;
44 import android.view.LayoutInflater;
45 import android.view.MenuItem;
46 import android.view.MotionEvent;
47 import android.view.View;
48 import android.view.ViewConfiguration;
49 import android.view.ContextMenu.ContextMenuInfo;
50 import android.widget.PopupWindow;
51 import android.widget.TextView;
53 import java.util.ArrayList;
54 import java.util.Calendar;
56 public class MonthView extends View implements View.OnCreateContextMenuListener {
58 private static final boolean PROFILE_LOAD_TIME = false;
60 private static float mScale = 0; // Used for supporting different screen densities
61 private static int WEEK_GAP = 0;
62 private static int MONTH_DAY_GAP = 1;
63 private static float HOUR_GAP = 0.5f;
64 private static int MONTH_DAY_TEXT_SIZE = 20;
65 private static int WEEK_BANNER_HEIGHT = 17;
66 private static int WEEK_TEXT_SIZE = 15;
67 private static int WEEK_TEXT_PADDING = 3;
68 private static int EVENT_DOT_TOP_MARGIN = 5;
69 private static int EVENT_DOT_LEFT_MARGIN = 7;
70 private static int EVENT_DOT_W_H = 10;
71 private static int EVENT_NUM_DAYS = 31;
72 private static int TEXT_BOTTOM_MARGIN = 7;
74 private static int HORIZONTAL_FLING_THRESHOLD = 50;
76 private int mCellHeight;
78 private boolean mLaunchDayView;
80 private GestureDetector mGestureDetector;
82 private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW;
85 private Time mViewCalendar;
86 private Time mSavedTime = new Time(); // the time when we entered this view
88 // This Time object is used to set the time for the other Month view.
89 private Time mOtherViewCalendar = new Time();
91 // This Time object is used for temporary calculations and is allocated
92 // once to avoid extra garbage collection
93 private Time mTempTime = new Time();
95 private DayOfMonthCursor mCursor;
97 private Drawable mBoxSelected;
98 private Drawable mBoxPressed;
99 private Drawable mBoxLongPressed;
100 private Drawable mEventDot;
101 private int mCellWidth;
103 private Resources mResources;
104 private MonthActivity mParentActivity;
105 private Navigator mNavigator;
106 private final EventGeometry mEventGeometry;
108 // Pre-allocate and reuse
109 private Rect mRect = new Rect();
111 //An array of which days have events for quick reference
112 private boolean[] eventDay = new boolean[31];
114 private PopupWindow mPopup;
115 private View mPopupView;
116 private static final int POPUP_HEIGHT = 100;
117 private int mPreviousPopupHeight;
118 private static final int POPUP_DISMISS_DELAY = 3000;
119 private DismissPopup mDismissPopup = new DismissPopup();
121 // For drawing to an off-screen Canvas
122 private Bitmap mBitmap;
123 private Canvas mCanvas;
124 private boolean mRedrawScreen = true;
125 private Rect mBitmapRect = new Rect();
126 private boolean mAnimating;
128 // These booleans disable features that were taken out of the spec.
129 private boolean mShowWeekNumbers = false;
130 private boolean mShowToast = false;
133 // These improve performance by minimizing calls to NinePatchDrawable.draw() for common
134 // drawables for day backgrounds.
135 // mDayBitmapCache is indexed by a unique integer constructed from the width/height.
136 private SparseArray<Bitmap> mDayBitmapCache = new SparseArray<Bitmap>(4);
138 private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
141 * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
143 private static final int SELECTION_HIDDEN = 0;
144 private static final int SELECTION_PRESSED = 1;
145 private static final int SELECTION_SELECTED = 2;
146 private static final int SELECTION_LONGPRESS = 3;
148 // Modulo used to pack (width,height) into a unique integer
149 private static final int MODULO_SHIFT = 16;
151 private int mSelectionMode = SELECTION_HIDDEN;
154 * The first Julian day of the current month.
156 private int mFirstJulianDay;
158 private int mStartDay;
160 private final EventLoader mEventLoader;
162 private ArrayList<Event> mEvents = new ArrayList<Event>();
164 private Drawable mTodayBackground;
165 private Drawable mDayBackground;
168 private int mMonthOtherMonthColor;
169 private int mMonthWeekBannerColor;
170 private int mMonthOtherMonthBannerColor;
171 private int mMonthOtherMonthDayNumberColor;
172 private int mMonthDayNumberColor;
173 private int mMonthTodayNumberColor;
174 private int mMonthSaturdayColor;
175 private int mMonthSundayColor;
177 public MonthView(MonthActivity activity, Navigator navigator) {
180 mScale = getContext().getResources().getDisplayMetrics().density;
183 MONTH_DAY_GAP *= mScale;
186 MONTH_DAY_TEXT_SIZE *= mScale;
187 WEEK_BANNER_HEIGHT *= mScale;
188 WEEK_TEXT_SIZE *= mScale;
189 WEEK_TEXT_PADDING *= mScale;
190 EVENT_DOT_TOP_MARGIN *= mScale;
191 EVENT_DOT_LEFT_MARGIN *= mScale;
192 EVENT_DOT_W_H *= mScale;
193 TEXT_BOTTOM_MARGIN *= mScale;
194 HORIZONTAL_FLING_THRESHOLD *= mScale;
198 mEventLoader = activity.mEventLoader;
199 mNavigator = navigator;
200 mEventGeometry = new EventGeometry();
201 mEventGeometry.setMinEventHeight(1.0f);
202 mEventGeometry.setHourGap(HOUR_GAP);
206 private void init(MonthActivity activity) {
209 setOnCreateContextMenuListener(this);
210 mParentActivity = activity;
211 mViewCalendar = new Time();
212 long now = System.currentTimeMillis();
213 mViewCalendar.set(now);
214 mViewCalendar.monthDay = 1;
215 long millis = mViewCalendar.normalize(true /* ignore DST */);
216 mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
217 mStartDay = Utils.getFirstDayOfWeek();
218 mViewCalendar.set(now);
220 mCursor = new DayOfMonthCursor(mViewCalendar.year, mViewCalendar.month,
221 mViewCalendar.monthDay, mParentActivity.getStartDay());
223 mToday.set(System.currentTimeMillis());
225 mResources = activity.getResources();
226 mBoxSelected = mResources.getDrawable(R.drawable.month_view_selected);
227 mBoxPressed = mResources.getDrawable(R.drawable.month_view_pressed);
228 mBoxLongPressed = mResources.getDrawable(R.drawable.month_view_longpress);
230 mEventDot = mResources.getDrawable(R.drawable.event_dot);
231 mTodayBackground = mResources.getDrawable(R.drawable.month_view_today_background);
232 mDayBackground = mResources.getDrawable(R.drawable.month_view_background);
234 // Cache color lookups
235 Resources res = getResources();
236 mMonthOtherMonthColor = res.getColor(R.color.month_other_month);
237 mMonthWeekBannerColor = res.getColor(R.color.month_week_banner);
238 mMonthOtherMonthBannerColor = res.getColor(R.color.month_other_month_banner);
239 mMonthOtherMonthDayNumberColor = res.getColor(R.color.month_other_month_day_number);
240 mMonthDayNumberColor = res.getColor(R.color.month_day_number);
241 mMonthTodayNumberColor = res.getColor(R.color.month_today_number);
242 mMonthSaturdayColor = res.getColor(R.color.month_saturday);
243 mMonthSundayColor = res.getColor(R.color.month_sunday);
246 LayoutInflater inflater;
247 inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
248 mPopupView = inflater.inflate(R.layout.month_bubble, null);
249 mPopup = new PopupWindow(activity);
250 mPopup.setContentView(mPopupView);
251 Resources.Theme dialogTheme = getResources().newTheme();
252 dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
253 TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
254 android.R.attr.windowBackground });
255 mPopup.setBackgroundDrawable(ta.getDrawable(0));
259 mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
261 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
263 // The user might do a slow "fling" after touching the screen
264 // and we don't want the long-press to pop up a context menu.
265 // Setting mLaunchDayView to false prevents the long-press.
266 mLaunchDayView = false;
267 mSelectionMode = SELECTION_HIDDEN;
269 int distanceX = Math.abs((int) e2.getX() - (int) e1.getX());
270 int distanceY = Math.abs((int) e2.getY() - (int) e1.getY());
271 if (distanceY < HORIZONTAL_FLING_THRESHOLD || distanceY < distanceX) {
275 // Switch to a different month
276 Time time = mOtherViewCalendar;
277 time.set(mViewCalendar);
283 time.normalize(true);
284 mParentActivity.goTo(time, true);
290 public boolean onDown(MotionEvent e) {
291 // Launch the Day/Agenda view when the finger lifts up,
292 // unless the finger moves before lifting up (onFling or onScroll).
293 mLaunchDayView = true;
297 public void setSelectedCell(MotionEvent e) {
298 int x = (int) e.getX();
299 int y = (int) e.getY();
300 int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
301 int col = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
309 // Highlight the selected day.
310 mCursor.setSelectedRowColumn(row, col);
314 public void onShowPress(MotionEvent e) {
315 // Highlight the selected day.
317 mSelectionMode = SELECTION_PRESSED;
318 mRedrawScreen = true;
323 public void onLongPress(MotionEvent e) {
324 // If mLaunchDayView is true, then we haven't done any scrolling
325 // after touching the screen, so allow long-press to proceed
326 // with popping up the context menu.
327 if (mLaunchDayView) {
328 mLaunchDayView = false;
329 mSelectionMode = SELECTION_LONGPRESS;
330 mRedrawScreen = true;
337 public boolean onScroll(MotionEvent e1, MotionEvent e2,
338 float distanceX, float distanceY) {
339 // If the user moves his finger after touching, then do not
340 // launch the Day view when he lifts his finger. Also, turn
341 // off the selection.
342 mLaunchDayView = false;
344 if (mSelectionMode != SELECTION_HIDDEN) {
345 mSelectionMode = SELECTION_HIDDEN;
346 mRedrawScreen = true;
353 public boolean onSingleTapUp(MotionEvent e) {
354 if (mLaunchDayView) {
356 mSelectionMode = SELECTION_SELECTED;
357 mRedrawScreen = true;
359 mLaunchDayView = false;
360 int x = (int) e.getX();
361 int y = (int) e.getY();
362 long millis = getSelectedMillisFor(x, y);
363 Utils.startActivity(getContext(), mDetailedView, millis);
371 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
374 final long startMillis = getSelectedTimeInMillis();
375 final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
376 | DateUtils.FORMAT_ABBREV_MONTH;
378 final String title = DateUtils.formatDateTime(mParentActivity, startMillis, flags);
379 menu.setHeaderTitle(title);
381 item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view);
382 item.setOnMenuItemClickListener(mContextMenuHandler);
383 item.setIcon(android.R.drawable.ic_menu_day);
384 item.setAlphabeticShortcut('d');
386 item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
387 item.setOnMenuItemClickListener(mContextMenuHandler);
388 item.setIcon(android.R.drawable.ic_menu_agenda);
389 item.setAlphabeticShortcut('a');
391 item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
392 item.setOnMenuItemClickListener(mContextMenuHandler);
393 item.setIcon(android.R.drawable.ic_menu_add);
394 item.setAlphabeticShortcut('n');
397 private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
398 public boolean onMenuItemClick(MenuItem item) {
399 switch (item.getItemId()) {
400 case MenuHelper.MENU_DAY: {
401 long startMillis = getSelectedTimeInMillis();
402 Utils.startActivity(mParentActivity, DayActivity.class.getName(), startMillis);
405 case MenuHelper.MENU_AGENDA: {
406 long startMillis = getSelectedTimeInMillis();
407 Utils.startActivity(mParentActivity, AgendaActivity.class.getName(), startMillis);
410 case MenuHelper.MENU_EVENT_CREATE: {
411 long startMillis = getSelectedTimeInMillis();
412 long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
413 Intent intent = new Intent(Intent.ACTION_VIEW);
414 intent.setClassName(mParentActivity, EditEvent.class.getName());
415 intent.putExtra(EVENT_BEGIN_TIME, startMillis);
416 intent.putExtra(EVENT_END_TIME, endMillis);
417 mParentActivity.startActivity(intent);
428 void reloadEvents() {
429 // Get the date for the beginning of the month
430 Time monthStart = mTempTime;
431 monthStart.set(mViewCalendar);
432 monthStart.monthDay = 1;
434 monthStart.minute = 0;
435 monthStart.second = 0;
436 long millis = monthStart.normalize(true /* ignore isDst */);
437 int startDay = Time.getJulianDay(millis, monthStart.gmtoff);
439 // Load the days with events in the background
440 mParentActivity.startProgressSpinner();
441 final long startMillis;
442 if (PROFILE_LOAD_TIME) {
443 startMillis = SystemClock.uptimeMillis();
445 // To avoid a compiler error that this variable might not be initialized.
449 mEventLoader.loadEventDaysInBackground(startDay, EVENT_NUM_DAYS, eventDay,
452 if (PROFILE_LOAD_TIME) {
453 long endMillis = SystemClock.uptimeMillis();
454 long elapsed = endMillis - startMillis;
455 Log.i("Cal", (mViewCalendar.month+1) + "/" + mViewCalendar.year +
456 " Month view load eventDays: " + elapsed);
458 //Refresh the screen when we're done and stop the spinner
459 mRedrawScreen = true;
460 mParentActivity.stopProgressSpinner();
466 void animationStarted() {
470 void animationFinished() {
472 mRedrawScreen = true;
477 protected void onSizeChanged(int width, int height, int oldw, int oldh) {
478 drawingCalc(width, height);
479 // If the size changed, then we should rebuild the bitmaps...
484 protected void onDetachedFromWindow() {
485 super.onDetachedFromWindow();
486 // No need to hang onto the bitmaps...
488 if (mBitmap != null) {
494 protected void onDraw(Canvas canvas) {
496 if (mCanvas == null) {
497 drawingCalc(getWidth(), getHeight());
500 // If we are zero-sized, the canvas will remain null so check again
501 if (mCanvas != null) {
502 // Clear the background
503 final Canvas bitmapCanvas = mCanvas;
504 bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
505 doDraw(bitmapCanvas);
506 mRedrawScreen = false;
510 // If we are zero-sized, the bitmap will be null so guard against this
511 if (mBitmap != null) {
512 canvas.drawBitmap(mBitmap, mBitmapRect, mBitmapRect, null);
516 private void doDraw(Canvas canvas) {
517 boolean isLandscape = getResources().getConfiguration().orientation
518 == Configuration.ORIENTATION_LANDSCAPE;
520 Paint p = new Paint();
522 int columnDay1 = mCursor.getColumnOf(1);
524 // Get the Julian day for the date at row 0, column 0.
525 int day = mFirstJulianDay - columnDay1;
528 Calendar calendar = null;
529 if (mShowWeekNumbers) {
530 calendar = Calendar.getInstance();
531 boolean noPrevMonth = (columnDay1 == 0);
533 // Compute the week number for the first row.
534 weekNum = getWeekOfYear(0, 0, noPrevMonth, calendar);
537 for (int row = 0; row < 6; row++) {
538 for (int column = 0; column < 7; column++) {
539 drawBox(day, weekNum, row, column, canvas, p, r, isLandscape);
543 if (mShowWeekNumbers) {
546 boolean inCurrentMonth = (day - mFirstJulianDay < 31);
547 weekNum = getWeekOfYear(row + 1, 0, inCurrentMonth, calendar);
556 public boolean onTouchEvent(MotionEvent event) {
557 if (mGestureDetector.onTouchEvent(event)) {
561 return super.onTouchEvent(event);
564 private long getSelectedMillisFor(int x, int y) {
565 int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
566 int column = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
571 DayOfMonthCursor c = mCursor;
572 Time time = mTempTime;
573 time.set(mViewCalendar);
575 // Compute the day number from the row and column. If the row and
576 // column are in a different month from the current one, then the
577 // monthDay might be negative or it might be greater than the number
578 // of days in this month, but that is okay because the normalize()
579 // method will adjust the month (and year) if necessary.
580 time.monthDay = 7 * row + column - c.getOffset() + 1;
581 return time.normalize(true);
585 * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect.
587 * @param drawable the drawable we wish to render
588 * @param width the width of the resulting bitmap
589 * @param height the height of the resulting bitmap
590 * @return a new bitmap
592 private Bitmap createBitmap(Drawable drawable, int width, int height) {
593 // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888)
594 Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
596 // Draw the drawable into the bitmap at the origin.
597 Canvas canvas = new Canvas(bitmap);
598 drawable.setBounds(0, 0, width, height);
599 drawable.draw(canvas);
604 * Clears the bitmap cache. Generally only needed when the screen size changed.
606 private void clearBitmapCache() {
607 recycleAndClearBitmapCache(mDayBitmapCache);
610 private void recycleAndClearBitmapCache(SparseArray<Bitmap> bitmapCache) {
611 int size = bitmapCache.size();
612 for(int i = 0; i < size; i++) {
613 bitmapCache.valueAt(i).recycle();
620 * Draw the grid lines for the calendar
621 * @param canvas The canvas to draw on.
622 * @param p The paint used for drawing.
624 private void drawGrid(Canvas canvas, Paint p) {
625 p.setColor(mMonthOtherMonthColor);
626 p.setAntiAlias(false);
628 final int width = getMeasuredWidth();
629 final int height = getMeasuredHeight();
631 for (int row = 0; row < 6; row++) {
632 int y = WEEK_GAP + row * (WEEK_GAP + mCellHeight) - 1;
633 canvas.drawLine(0, y, width, y, p);
635 for (int column = 1; column < 7; column++) {
636 int x = mBorder + column * (MONTH_DAY_GAP + mCellWidth) - 1;
637 canvas.drawLine(x, WEEK_GAP, x, height, p);
642 * Draw a single box onto the canvas.
643 * @param day The Julian day.
644 * @param weekNum The week number.
645 * @param row The row of the box (0-5).
646 * @param column The column of the box (0-6).
647 * @param canvas The canvas to draw on.
648 * @param p The paint used for drawing.
649 * @param r The rectangle used for each box.
650 * @param isLandscape Is the current orientation landscape.
652 private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p,
653 Rect r, boolean isLandscape) {
655 // Only draw the selection if we are in the press state or if we have
656 // moved the cursor with key input.
657 boolean drawSelection = false;
658 if (mSelectionMode != SELECTION_HIDDEN) {
659 drawSelection = mCursor.isSelected(row, column);
662 boolean withinCurrentMonth = mCursor.isWithinCurrentMonth(row, column);
663 boolean isToday = false;
664 int dayOfBox = mCursor.getDayAt(row, column);
665 if (dayOfBox == mToday.monthDay && mCursor.getYear() == mToday.year
666 && mCursor.getMonth() == mToday.month) {
670 int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight);
671 int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth);
675 r.right = x + mCellWidth;
676 r.bottom = y + mCellHeight;
679 // Adjust the left column, right column, and bottom row to leave
683 } else if (column == 6) {
684 r.right += mBorder + 2;
688 r.bottom = getMeasuredHeight();
691 // Draw the cell contents (excluding monthDay number)
692 if (!withinCurrentMonth) {
693 boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column);
695 // Adjust cell boundaries to compensate for the different border
701 } else if (drawSelection) {
702 if (mSelectionMode == SELECTION_SELECTED) {
703 mBoxSelected.setBounds(r);
704 mBoxSelected.draw(canvas);
705 } else if (mSelectionMode == SELECTION_PRESSED) {
706 mBoxPressed.setBounds(r);
707 mBoxPressed.draw(canvas);
709 mBoxLongPressed.setBounds(r);
710 mBoxLongPressed.draw(canvas);
713 //Places a green dot in the upper left corner of the day if there is
714 //an event on that day
715 if (withinCurrentMonth && eventDay[day-mFirstJulianDay]) {
716 drawEvents(day, canvas, r, p);
719 updateEventDetails(day);
722 // Today gets a different background
724 // We could cache this for a little bit more performance, but it's not on the
725 // performance radar...
726 Drawable background = mTodayBackground;
727 background.setBounds(r);
728 background.draw(canvas);
730 // Use the bitmap cache to draw the day background
731 int width = r.right - r.left;
732 int height = r.bottom - r.top;
733 // Compute a unique id that depends on width and height.
734 int id = (height << MODULO_SHIFT) | width;
735 Bitmap bitmap = mDayBitmapCache.get(id);
736 if (bitmap == null) {
737 bitmap = createBitmap(mDayBackground, width, height);
738 mDayBitmapCache.put(id, bitmap);
740 canvas.drawBitmap(bitmap, r.left, r.top, p);
742 //Places a green dot near the top of the day if there is
743 //an event on that day
744 if (withinCurrentMonth && eventDay[day-mFirstJulianDay]) {
745 drawEvents(day, canvas, r, p);
750 if (mShowWeekNumbers && column == 0) {
752 p.setStyle(Paint.Style.FILL);
753 p.setColor(mMonthWeekBannerColor);
755 int bottom = r.bottom;
756 r.bottom = r.top + WEEK_BANNER_HEIGHT;
758 canvas.drawRect(r, p);
763 r.top = r.bottom - WEEK_BANNER_HEIGHT;
765 canvas.drawRect(r, p);
771 p.setColor(mMonthOtherMonthBannerColor);
772 p.setAntiAlias(true);
774 p.setTextSize(WEEK_TEXT_SIZE);
775 p.setTextAlign(Paint.Align.LEFT);
777 int textX = r.left + WEEK_TEXT_PADDING;
780 textY = r.top + WEEK_BANNER_HEIGHT - WEEK_TEXT_PADDING;
782 textY = r.bottom - WEEK_TEXT_PADDING;
785 canvas.drawText(String.valueOf(weekNum), textX, textY, p);
788 // Draw the monthDay number
789 p.setStyle(Paint.Style.FILL);
790 p.setAntiAlias(true);
792 p.setTextSize(MONTH_DAY_TEXT_SIZE);
794 if (!withinCurrentMonth) {
795 p.setColor(mMonthOtherMonthDayNumberColor);
797 if (isToday && !drawSelection) {
798 p.setColor(mMonthTodayNumberColor);
799 } else if (Utils.isSunday(column, mStartDay)) {
800 p.setColor(mMonthSundayColor);
801 } else if (Utils.isSaturday(column, mStartDay)) {
802 p.setColor(mMonthSaturdayColor);
804 p.setColor(mMonthDayNumberColor);
806 //bolds the day if there's an event that day
807 p.setFakeBoldText(eventDay[day-mFirstJulianDay]);
809 /*Drawing of day number is done here
810 *easy to find tags draw number draw day*/
811 p.setTextAlign(Paint.Align.CENTER);
813 int textX = r.left + (right - r.left) / 2; // center of text
814 int textY = r.bottom - TEXT_BOTTOM_MARGIN - 2; // bottom of text
815 canvas.drawText(String.valueOf(mCursor.getDayAt(row, column)), textX, textY, p);
818 ///Create and draw an event dot for this day
819 private void drawEvents(int date, Canvas canvas, Rect rect, Paint p)
821 //Coords of the upper left corner where we'll draw the event dot
822 int top = rect.top + EVENT_DOT_TOP_MARGIN;
823 int left = (rect.left + rect.right-EVENT_DOT_W_H)/2;
824 Bitmap bitmap = createBitmap(mEventDot, EVENT_DOT_W_H, EVENT_DOT_W_H);
826 canvas.drawBitmap(bitmap, left, top, p);
829 private boolean isFirstDayOfNextMonth(int row, int column) {
836 return mCursor.isWithinCurrentMonth(row, column);
839 private int getWeekOfYear(int row, int column, boolean isWithinCurrentMonth,
841 calendar.set(Calendar.DAY_OF_MONTH, mCursor.getDayAt(row, column));
842 if (isWithinCurrentMonth) {
843 calendar.set(Calendar.MONTH, mCursor.getMonth());
844 calendar.set(Calendar.YEAR, mCursor.getYear());
846 int month = mCursor.getMonth();
847 int year = mCursor.getYear();
865 calendar.set(Calendar.MONTH, month);
866 calendar.set(Calendar.YEAR, year);
869 return calendar.get(Calendar.WEEK_OF_YEAR);
872 void setDetailedView(String detailedView) {
873 mDetailedView = detailedView;
876 void setSelectedTime(Time time) {
877 // Save the selected time so that we can restore it later when we switch views.
878 mSavedTime.set(time);
880 mViewCalendar.set(time);
881 mViewCalendar.monthDay = 1;
882 long millis = mViewCalendar.normalize(true /* ignore DST */);
883 mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
884 mViewCalendar.set(time);
886 mCursor = new DayOfMonthCursor(time.year, time.month, time.monthDay,
887 mCursor.getWeekStartDay());
889 mRedrawScreen = true;
893 public long getSelectedTimeInMillis() {
894 Time time = mTempTime;
895 time.set(mViewCalendar);
897 time.month += mCursor.getSelectedMonthOffset();
898 time.monthDay = mCursor.getSelectedDayOfMonth();
900 // Restore the saved hour:minute:second offset from when we entered
902 time.second = mSavedTime.second;
903 time.minute = mSavedTime.minute;
904 time.hour = mSavedTime.hour;
905 return time.normalize(true);
909 return mViewCalendar;
912 public int getSelectionMode() {
913 return mSelectionMode;
916 public void setSelectionMode(int selectionMode) {
917 mSelectionMode = selectionMode;
920 private void drawingCalc(int width, int height) {
921 mCellHeight = (height - (6 * WEEK_GAP)) / 6;
922 mEventGeometry.setHourHeight((mCellHeight - 25.0f * HOUR_GAP) / 24.0f);
923 mCellWidth = (width - (6 * MONTH_DAY_GAP)) / 7;
924 mBorder = (width - 6 * (mCellWidth + MONTH_DAY_GAP) - mCellWidth) / 2;
928 mPopup.setWidth(width - 20);
929 mPopup.setHeight(POPUP_HEIGHT);
932 if (((mBitmap == null)
933 || mBitmap.isRecycled()
934 || (mBitmap.getHeight() != height)
935 || (mBitmap.getWidth() != width))
936 && (width > 0) && (height > 0)) {
937 if (mBitmap != null) {
940 mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
941 mCanvas = new Canvas(mBitmap);
945 mBitmapRect.bottom = height;
946 mBitmapRect.left = 0;
947 mBitmapRect.right = width;
950 private void updateEventDetails(int date) {
955 getHandler().removeCallbacks(mDismissPopup);
956 ArrayList<Event> events = mEvents;
957 int numEvents = events.size();
958 if (numEvents == 0) {
964 for (int i = 0; i < numEvents; i++) {
965 Event event = events.get(i);
967 if (event.startDay > date || event.endDay < date) {
971 // If we have all the event that we can display, then just count
973 if (eventIndex >= 4) {
979 boolean showEndTime = false;
981 int numDays = event.endDay - event.startDay;
983 flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
984 | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
987 flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
988 | DateUtils.FORMAT_ABBREV_ALL;
991 flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
992 if (DateFormat.is24HourFormat(mParentActivity)) {
993 flags |= DateUtils.FORMAT_24HOUR;
999 timeRange = DateUtils.formatDateRange(mParentActivity,
1000 event.startMillis, event.endMillis, flags);
1002 timeRange = DateUtils.formatDateRange(mParentActivity,
1003 event.startMillis, event.startMillis, flags);
1006 TextView timeView = null;
1007 TextView titleView = null;
1008 switch (eventIndex) {
1010 timeView = (TextView) mPopupView.findViewById(R.id.time0);
1011 titleView = (TextView) mPopupView.findViewById(R.id.event_title0);
1014 timeView = (TextView) mPopupView.findViewById(R.id.time1);
1015 titleView = (TextView) mPopupView.findViewById(R.id.event_title1);
1018 timeView = (TextView) mPopupView.findViewById(R.id.time2);
1019 titleView = (TextView) mPopupView.findViewById(R.id.event_title2);
1022 timeView = (TextView) mPopupView.findViewById(R.id.time3);
1023 titleView = (TextView) mPopupView.findViewById(R.id.event_title3);
1027 timeView.setText(timeRange);
1028 titleView.setText(event.title);
1031 if (eventIndex == 0) {
1032 // We didn't find any events for this day
1037 // Hide the items that have no event information
1039 switch (eventIndex) {
1041 view = mPopupView.findViewById(R.id.item_layout1);
1042 view.setVisibility(View.GONE);
1043 view = mPopupView.findViewById(R.id.item_layout2);
1044 view.setVisibility(View.GONE);
1045 view = mPopupView.findViewById(R.id.item_layout3);
1046 view.setVisibility(View.GONE);
1047 view = mPopupView.findViewById(R.id.plus_more);
1048 view.setVisibility(View.GONE);
1051 view = mPopupView.findViewById(R.id.item_layout1);
1052 view.setVisibility(View.VISIBLE);
1053 view = mPopupView.findViewById(R.id.item_layout2);
1054 view.setVisibility(View.GONE);
1055 view = mPopupView.findViewById(R.id.item_layout3);
1056 view.setVisibility(View.GONE);
1057 view = mPopupView.findViewById(R.id.plus_more);
1058 view.setVisibility(View.GONE);
1061 view = mPopupView.findViewById(R.id.item_layout1);
1062 view.setVisibility(View.VISIBLE);
1063 view = mPopupView.findViewById(R.id.item_layout2);
1064 view.setVisibility(View.VISIBLE);
1065 view = mPopupView.findViewById(R.id.item_layout3);
1066 view.setVisibility(View.GONE);
1067 view = mPopupView.findViewById(R.id.plus_more);
1068 view.setVisibility(View.GONE);
1071 view = mPopupView.findViewById(R.id.item_layout1);
1072 view.setVisibility(View.VISIBLE);
1073 view = mPopupView.findViewById(R.id.item_layout2);
1074 view.setVisibility(View.VISIBLE);
1075 view = mPopupView.findViewById(R.id.item_layout3);
1076 view.setVisibility(View.VISIBLE);
1077 view = mPopupView.findViewById(R.id.plus_more);
1078 view.setVisibility(View.GONE);
1081 view = mPopupView.findViewById(R.id.item_layout1);
1082 view.setVisibility(View.VISIBLE);
1083 view = mPopupView.findViewById(R.id.item_layout2);
1084 view.setVisibility(View.VISIBLE);
1085 view = mPopupView.findViewById(R.id.item_layout3);
1086 view.setVisibility(View.VISIBLE);
1087 TextView tv = (TextView) mPopupView.findViewById(R.id.plus_more);
1088 tv.setVisibility(View.VISIBLE);
1089 String format = mResources.getString(R.string.plus_N_more);
1090 String plusMore = String.format(format, eventIndex - 4);
1091 tv.setText(plusMore);
1095 if (eventIndex > 5) {
1098 int popupHeight = 20 * eventIndex + 15;
1099 mPopup.setHeight(popupHeight);
1101 if (mPreviousPopupHeight != popupHeight) {
1102 mPreviousPopupHeight = popupHeight;
1105 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
1106 postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
1110 public boolean onKeyUp(int keyCode, KeyEvent event) {
1111 long duration = event.getEventTime() - event.getDownTime();
1114 case KeyEvent.KEYCODE_DPAD_CENTER:
1115 if (mSelectionMode == SELECTION_HIDDEN) {
1116 // Don't do anything unless the selection is visible.
1120 if (mSelectionMode == SELECTION_PRESSED) {
1121 // This was the first press when there was nothing selected.
1122 // Change the selection from the "pressed" state to the
1123 // the "selected" state. We treat short-press and
1124 // long-press the same here because nothing was selected.
1125 mSelectionMode = SELECTION_SELECTED;
1126 mRedrawScreen = true;
1131 // Check the duration to determine if this was a short press
1132 if (duration < ViewConfiguration.getLongPressTimeout()) {
1133 long millis = getSelectedTimeInMillis();
1134 Utils.startActivity(getContext(), mDetailedView, millis);
1136 mSelectionMode = SELECTION_LONGPRESS;
1137 mRedrawScreen = true;
1142 return super.onKeyUp(keyCode, event);
1146 public boolean onKeyDown(int keyCode, KeyEvent event) {
1147 if (mSelectionMode == SELECTION_HIDDEN) {
1148 if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
1149 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
1150 || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
1151 // Display the selection box but don't move or select it
1152 // on this key press.
1153 mSelectionMode = SELECTION_SELECTED;
1154 mRedrawScreen = true;
1157 } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
1158 // Display the selection box but don't select it
1159 // on this key press.
1160 mSelectionMode = SELECTION_PRESSED;
1161 mRedrawScreen = true;
1167 mSelectionMode = SELECTION_SELECTED;
1168 boolean redraw = false;
1172 case KeyEvent.KEYCODE_ENTER:
1173 long millis = getSelectedTimeInMillis();
1174 Utils.startActivity(getContext(), mDetailedView, millis);
1176 case KeyEvent.KEYCODE_DPAD_UP:
1178 other = mOtherViewCalendar;
1179 other.set(mViewCalendar);
1181 other.monthDay = mCursor.getSelectedDayOfMonth();
1183 // restore the calendar cursor for the animation
1189 case KeyEvent.KEYCODE_DPAD_DOWN:
1190 if (mCursor.down()) {
1191 other = mOtherViewCalendar;
1192 other.set(mViewCalendar);
1194 other.monthDay = mCursor.getSelectedDayOfMonth();
1196 // restore the calendar cursor for the animation
1202 case KeyEvent.KEYCODE_DPAD_LEFT:
1203 if (mCursor.left()) {
1204 other = mOtherViewCalendar;
1205 other.set(mViewCalendar);
1207 other.monthDay = mCursor.getSelectedDayOfMonth();
1209 // restore the calendar cursor for the animation
1215 case KeyEvent.KEYCODE_DPAD_RIGHT:
1216 if (mCursor.right()) {
1217 other = mOtherViewCalendar;
1218 other.set(mViewCalendar);
1220 other.monthDay = mCursor.getSelectedDayOfMonth();
1222 // restore the calendar cursor for the animation
1229 if (other != null) {
1230 other.normalize(true /* ignore DST */);
1231 mNavigator.goTo(other, true);
1232 } else if (redraw) {
1233 mRedrawScreen = true;
1240 class DismissPopup implements Runnable {
1246 // This is called when the activity is paused so that the popup can
1248 void dismissPopup() {
1253 // Protect against null-pointer exceptions
1254 if (mPopup != null) {
1258 Handler handler = getHandler();
1259 if (handler != null) {
1260 handler.removeCallbacks(mDismissPopup);