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.Color;
30 import android.graphics.Paint;
31 import android.graphics.PorterDuff;
32 import android.graphics.Rect;
33 import android.graphics.RectF;
34 import android.graphics.Paint.Style;
35 import android.graphics.drawable.Drawable;
36 import android.os.Handler;
37 import android.os.SystemClock;
38 import android.provider.Calendar.Attendees;
39 import android.text.format.DateFormat;
40 import android.text.format.DateUtils;
41 import android.text.format.Time;
42 import android.util.Log;
43 import android.util.SparseArray;
44 import android.view.ContextMenu;
45 import android.view.GestureDetector;
46 import android.view.Gravity;
47 import android.view.KeyEvent;
48 import android.view.LayoutInflater;
49 import android.view.MenuItem;
50 import android.view.MotionEvent;
51 import android.view.View;
52 import android.view.ViewConfiguration;
53 import android.view.ContextMenu.ContextMenuInfo;
54 import android.widget.PopupWindow;
55 import android.widget.TextView;
57 import java.util.ArrayList;
58 import java.util.Calendar;
60 public class MonthView extends View implements View.OnCreateContextMenuListener {
62 private static final boolean PROFILE_LOAD_TIME = false;
64 private static float mScale = 0; // Used for supporting different screen densities
65 private static int WEEK_GAP = 0;
66 private static int MONTH_DAY_GAP = 1;
67 private static float HOUR_GAP = 0f;
68 private static float MIN_EVENT_HEIGHT = 4f;
69 private static int MONTH_DAY_TEXT_SIZE = 20;
70 private static int WEEK_BANNER_HEIGHT = 17;
71 private static int WEEK_TEXT_SIZE = 15;
72 private static int WEEK_TEXT_PADDING = 3;
73 private static int EVENT_DOT_TOP_MARGIN = 5;
74 private static int EVENT_DOT_LEFT_MARGIN = 7;
75 private static int EVENT_DOT_W_H = 10;
76 private static int EVENT_NUM_DAYS = 31;
77 private static int TEXT_TOP_MARGIN = 7;
78 private static int BUSY_BITS_WIDTH = 6;
79 private static int BUSY_BITS_MARGIN = 4;
80 private static int DAY_NUMBER_OFFSET = 10;
82 private static int HORIZONTAL_FLING_THRESHOLD = 50;
84 private static final int BUSY_BITS_COLOR = 0xFF6090F0;
85 private static final int LIGHT_GRAY_BGCOLOR = 0xFFDDDDDD;
87 private int mCellHeight;
89 private boolean mLaunchDayView;
91 private GestureDetector mGestureDetector;
93 private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW;
96 private Time mViewCalendar;
97 private Time mSavedTime = new Time(); // the time when we entered this view
99 // This Time object is used to set the time for the other Month view.
100 private Time mOtherViewCalendar = new Time();
102 // This Time object is used for temporary calculations and is allocated
103 // once to avoid extra garbage collection
104 private Time mTempTime = new Time();
106 private DayOfMonthCursor mCursor;
108 private Drawable mBoxSelected;
109 private Drawable mBoxPressed;
110 private Drawable mBoxLongPressed;
111 private Drawable mEventDot;
112 private int mCellWidth;
114 private Resources mResources;
115 private MonthActivity mParentActivity;
116 private Navigator mNavigator;
117 private final EventGeometry mEventGeometry;
119 // Pre-allocate and reuse
120 private Rect mRect = new Rect();
122 //An array of which days have events for quick reference
123 private boolean[] eventDay = new boolean[31];
125 private PopupWindow mPopup;
126 private View mPopupView;
127 private static final int POPUP_HEIGHT = 100;
128 private int mPreviousPopupHeight;
129 private static final int POPUP_DISMISS_DELAY = 3000;
130 private DismissPopup mDismissPopup = new DismissPopup();
132 // For drawing to an off-screen Canvas
133 private Bitmap mBitmap;
134 private Canvas mCanvas;
135 private boolean mRedrawScreen = true;
136 private Rect mBitmapRect = new Rect();
137 private RectF mRectF = new RectF();
138 private boolean mAnimating;
140 // These booleans disable features that were taken out of the spec.
141 private boolean mShowWeekNumbers = false;
142 private boolean mShowToast = false;
145 // These improve performance by minimizing calls to NinePatchDrawable.draw() for common
146 // drawables for day backgrounds.
147 // mDayBitmapCache is indexed by a unique integer constructed from the width/height.
148 private SparseArray<Bitmap> mDayBitmapCache = new SparseArray<Bitmap>(4);
150 private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
153 * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
155 private static final int SELECTION_HIDDEN = 0;
156 private static final int SELECTION_PRESSED = 1;
157 private static final int SELECTION_SELECTED = 2;
158 private static final int SELECTION_LONGPRESS = 3;
160 // Modulo used to pack (width,height) into a unique integer
161 private static final int MODULO_SHIFT = 16;
163 private int mSelectionMode = SELECTION_HIDDEN;
166 * The first Julian day of the current month.
168 private int mFirstJulianDay;
170 private int mStartDay;
172 private final EventLoader mEventLoader;
174 private ArrayList<Event> mEvents = new ArrayList<Event>();
176 private Drawable mTodayBackground;
179 private int mMonthOtherMonthColor;
180 private int mMonthWeekBannerColor;
181 private int mMonthOtherMonthBannerColor;
182 private int mMonthOtherMonthDayNumberColor;
183 private int mMonthDayNumberColor;
184 private int mMonthTodayNumberColor;
185 private int mMonthSaturdayColor;
186 private int mMonthSundayColor;
188 public MonthView(MonthActivity activity, Navigator navigator) {
191 mScale = getContext().getResources().getDisplayMetrics().density;
194 MONTH_DAY_GAP *= mScale;
197 MONTH_DAY_TEXT_SIZE *= mScale;
198 WEEK_BANNER_HEIGHT *= mScale;
199 WEEK_TEXT_SIZE *= mScale;
200 WEEK_TEXT_PADDING *= mScale;
201 EVENT_DOT_TOP_MARGIN *= mScale;
202 EVENT_DOT_LEFT_MARGIN *= mScale;
203 EVENT_DOT_W_H *= mScale;
204 TEXT_TOP_MARGIN *= mScale;
205 HORIZONTAL_FLING_THRESHOLD *= mScale;
206 MIN_EVENT_HEIGHT *= mScale;
207 BUSY_BITS_WIDTH *= mScale;
208 BUSY_BITS_MARGIN *= mScale;
209 DAY_NUMBER_OFFSET *= mScale;
213 mEventLoader = activity.mEventLoader;
214 mNavigator = navigator;
215 mEventGeometry = new EventGeometry();
216 mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
217 mEventGeometry.setHourGap(HOUR_GAP);
221 private void init(MonthActivity activity) {
224 setOnCreateContextMenuListener(this);
225 mParentActivity = activity;
226 mViewCalendar = new Time();
227 long now = System.currentTimeMillis();
228 mViewCalendar.set(now);
229 mViewCalendar.monthDay = 1;
230 long millis = mViewCalendar.normalize(true /* ignore DST */);
231 mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
232 mStartDay = Utils.getFirstDayOfWeek();
233 mViewCalendar.set(now);
235 mCursor = new DayOfMonthCursor(mViewCalendar.year, mViewCalendar.month,
236 mViewCalendar.monthDay, mParentActivity.getStartDay());
238 mToday.set(System.currentTimeMillis());
240 mResources = activity.getResources();
241 mBoxSelected = mResources.getDrawable(R.drawable.month_view_selected);
242 mBoxPressed = mResources.getDrawable(R.drawable.month_view_pressed);
243 mBoxLongPressed = mResources.getDrawable(R.drawable.month_view_longpress);
245 mEventDot = mResources.getDrawable(R.drawable.event_dot);
246 mTodayBackground = mResources.getDrawable(R.drawable.month_view_today_background);
248 // Cache color lookups
249 Resources res = getResources();
250 mMonthOtherMonthColor = res.getColor(R.color.month_other_month);
251 mMonthWeekBannerColor = res.getColor(R.color.month_week_banner);
252 mMonthOtherMonthBannerColor = res.getColor(R.color.month_other_month_banner);
253 mMonthOtherMonthDayNumberColor = res.getColor(R.color.month_other_month_day_number);
254 mMonthDayNumberColor = res.getColor(R.color.month_day_number);
255 mMonthTodayNumberColor = res.getColor(R.color.month_today_number);
256 mMonthSaturdayColor = res.getColor(R.color.month_saturday);
257 mMonthSundayColor = res.getColor(R.color.month_sunday);
260 LayoutInflater inflater;
261 inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
262 mPopupView = inflater.inflate(R.layout.month_bubble, null);
263 mPopup = new PopupWindow(activity);
264 mPopup.setContentView(mPopupView);
265 Resources.Theme dialogTheme = getResources().newTheme();
266 dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
267 TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
268 android.R.attr.windowBackground });
269 mPopup.setBackgroundDrawable(ta.getDrawable(0));
273 mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
275 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
277 // The user might do a slow "fling" after touching the screen
278 // and we don't want the long-press to pop up a context menu.
279 // Setting mLaunchDayView to false prevents the long-press.
280 mLaunchDayView = false;
281 mSelectionMode = SELECTION_HIDDEN;
283 int distanceX = Math.abs((int) e2.getX() - (int) e1.getX());
284 int distanceY = Math.abs((int) e2.getY() - (int) e1.getY());
285 if (distanceY < HORIZONTAL_FLING_THRESHOLD || distanceY < distanceX) {
289 // Switch to a different month
290 Time time = mOtherViewCalendar;
291 time.set(mViewCalendar);
297 time.normalize(true);
298 mParentActivity.goTo(time, true);
304 public boolean onDown(MotionEvent e) {
305 // Launch the Day/Agenda view when the finger lifts up,
306 // unless the finger moves before lifting up (onFling or onScroll).
307 mLaunchDayView = true;
311 public void setSelectedCell(MotionEvent e) {
312 int x = (int) e.getX();
313 int y = (int) e.getY();
314 int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
315 int col = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
323 // Highlight the selected day.
324 mCursor.setSelectedRowColumn(row, col);
328 public void onShowPress(MotionEvent e) {
329 // Highlight the selected day.
331 mSelectionMode = SELECTION_PRESSED;
332 mRedrawScreen = true;
337 public void onLongPress(MotionEvent e) {
338 // If mLaunchDayView is true, then we haven't done any scrolling
339 // after touching the screen, so allow long-press to proceed
340 // with popping up the context menu.
341 if (mLaunchDayView) {
342 mLaunchDayView = false;
343 mSelectionMode = SELECTION_LONGPRESS;
344 mRedrawScreen = true;
351 public boolean onScroll(MotionEvent e1, MotionEvent e2,
352 float distanceX, float distanceY) {
353 // If the user moves his finger after touching, then do not
354 // launch the Day view when he lifts his finger. Also, turn
355 // off the selection.
356 mLaunchDayView = false;
358 if (mSelectionMode != SELECTION_HIDDEN) {
359 mSelectionMode = SELECTION_HIDDEN;
360 mRedrawScreen = true;
367 public boolean onSingleTapUp(MotionEvent e) {
368 if (mLaunchDayView) {
370 mSelectionMode = SELECTION_SELECTED;
371 mRedrawScreen = true;
373 mLaunchDayView = false;
374 int x = (int) e.getX();
375 int y = (int) e.getY();
376 long millis = getSelectedMillisFor(x, y);
377 Utils.startActivity(getContext(), mDetailedView, millis);
385 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
388 final long startMillis = getSelectedTimeInMillis();
389 final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
390 | DateUtils.FORMAT_ABBREV_MONTH;
392 final String title = DateUtils.formatDateTime(mParentActivity, startMillis, flags);
393 menu.setHeaderTitle(title);
395 item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.show_day_view);
396 item.setOnMenuItemClickListener(mContextMenuHandler);
397 item.setIcon(android.R.drawable.ic_menu_day);
398 item.setAlphabeticShortcut('d');
400 item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.show_agenda_view);
401 item.setOnMenuItemClickListener(mContextMenuHandler);
402 item.setIcon(android.R.drawable.ic_menu_agenda);
403 item.setAlphabeticShortcut('a');
405 item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
406 item.setOnMenuItemClickListener(mContextMenuHandler);
407 item.setIcon(android.R.drawable.ic_menu_add);
408 item.setAlphabeticShortcut('n');
411 private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
412 public boolean onMenuItemClick(MenuItem item) {
413 switch (item.getItemId()) {
414 case MenuHelper.MENU_DAY: {
415 long startMillis = getSelectedTimeInMillis();
416 Utils.startActivity(mParentActivity, DayActivity.class.getName(), startMillis);
419 case MenuHelper.MENU_AGENDA: {
420 long startMillis = getSelectedTimeInMillis();
421 Utils.startActivity(mParentActivity, AgendaActivity.class.getName(), startMillis);
424 case MenuHelper.MENU_EVENT_CREATE: {
425 long startMillis = getSelectedTimeInMillis();
426 long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
427 Intent intent = new Intent(Intent.ACTION_VIEW);
428 intent.setClassName(mParentActivity, EditEvent.class.getName());
429 intent.putExtra(EVENT_BEGIN_TIME, startMillis);
430 intent.putExtra(EVENT_END_TIME, endMillis);
431 mParentActivity.startActivity(intent);
442 void reloadEvents() {
443 // Get the date for the beginning of the month
444 Time monthStart = mTempTime;
445 monthStart.set(mViewCalendar);
446 monthStart.monthDay = 1;
448 monthStart.minute = 0;
449 monthStart.second = 0;
450 long millis = monthStart.normalize(true /* ignore isDst */);
451 int startDay = Time.getJulianDay(millis, monthStart.gmtoff);
453 // Load the days with events in the background
454 mParentActivity.startProgressSpinner();
455 final long startMillis;
456 if (PROFILE_LOAD_TIME) {
457 startMillis = SystemClock.uptimeMillis();
459 // To avoid a compiler error that this variable might not be initialized.
463 final ArrayList<Event> events = new ArrayList<Event>();
464 mEventLoader.loadEventsInBackground(EVENT_NUM_DAYS, events, millis, new Runnable() {
467 mRedrawScreen = true;
468 mParentActivity.stopProgressSpinner();
470 int numEvents = events.size();
472 // Clear out event days
473 for (int i = 0; i < EVENT_NUM_DAYS; i++) {
477 // Compute the new set of days with events
478 for (int i = 0; i < numEvents; i++) {
479 Event event = events.get(i);
480 int startDay = event.startDay - mFirstJulianDay;
481 int endDay = event.endDay - mFirstJulianDay + 1;
482 if (startDay < 31 || endDay >= 0) {
495 for (int j = startDay; j < endDay; j++) {
504 void animationStarted() {
508 void animationFinished() {
510 mRedrawScreen = true;
515 protected void onSizeChanged(int width, int height, int oldw, int oldh) {
516 drawingCalc(width, height);
517 // If the size changed, then we should rebuild the bitmaps...
522 protected void onDetachedFromWindow() {
523 super.onDetachedFromWindow();
524 // No need to hang onto the bitmaps...
526 if (mBitmap != null) {
532 protected void onDraw(Canvas canvas) {
534 if (mCanvas == null) {
535 drawingCalc(getWidth(), getHeight());
538 // If we are zero-sized, the canvas will remain null so check again
539 if (mCanvas != null) {
540 // Clear the background
541 final Canvas bitmapCanvas = mCanvas;
542 bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
543 doDraw(bitmapCanvas);
544 mRedrawScreen = false;
548 // If we are zero-sized, the bitmap will be null so guard against this
549 if (mBitmap != null) {
550 canvas.drawBitmap(mBitmap, mBitmapRect, mBitmapRect, null);
554 private void doDraw(Canvas canvas) {
555 boolean isLandscape = getResources().getConfiguration().orientation
556 == Configuration.ORIENTATION_LANDSCAPE;
558 Paint p = new Paint();
560 int columnDay1 = mCursor.getColumnOf(1);
562 // Get the Julian day for the date at row 0, column 0.
563 int day = mFirstJulianDay - columnDay1;
566 Calendar calendar = null;
567 if (mShowWeekNumbers) {
568 calendar = Calendar.getInstance();
569 boolean noPrevMonth = (columnDay1 == 0);
571 // Compute the week number for the first row.
572 weekNum = getWeekOfYear(0, 0, noPrevMonth, calendar);
575 for (int row = 0; row < 6; row++) {
576 for (int column = 0; column < 7; column++) {
577 drawBox(day, weekNum, row, column, canvas, p, r, isLandscape);
581 if (mShowWeekNumbers) {
584 boolean inCurrentMonth = (day - mFirstJulianDay < 31);
585 weekNum = getWeekOfYear(row + 1, 0, inCurrentMonth, calendar);
594 public boolean onTouchEvent(MotionEvent event) {
595 if (mGestureDetector.onTouchEvent(event)) {
599 return super.onTouchEvent(event);
602 private long getSelectedMillisFor(int x, int y) {
603 int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
604 int column = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
609 DayOfMonthCursor c = mCursor;
610 Time time = mTempTime;
611 time.set(mViewCalendar);
613 // Compute the day number from the row and column. If the row and
614 // column are in a different month from the current one, then the
615 // monthDay might be negative or it might be greater than the number
616 // of days in this month, but that is okay because the normalize()
617 // method will adjust the month (and year) if necessary.
618 time.monthDay = 7 * row + column - c.getOffset() + 1;
619 return time.normalize(true);
623 * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect.
625 * @param drawable the drawable we wish to render
626 * @param width the width of the resulting bitmap
627 * @param height the height of the resulting bitmap
628 * @return a new bitmap
630 private Bitmap createBitmap(Drawable drawable, int width, int height) {
631 // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888)
632 Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
634 // Draw the drawable into the bitmap at the origin.
635 Canvas canvas = new Canvas(bitmap);
636 drawable.setBounds(0, 0, width, height);
637 drawable.draw(canvas);
642 * Clears the bitmap cache. Generally only needed when the screen size changed.
644 private void clearBitmapCache() {
645 recycleAndClearBitmapCache(mDayBitmapCache);
648 private void recycleAndClearBitmapCache(SparseArray<Bitmap> bitmapCache) {
649 int size = bitmapCache.size();
650 for(int i = 0; i < size; i++) {
651 bitmapCache.valueAt(i).recycle();
658 * Draw the grid lines for the calendar
659 * @param canvas The canvas to draw on.
660 * @param p The paint used for drawing.
662 private void drawGrid(Canvas canvas, Paint p) {
663 p.setColor(mMonthOtherMonthColor);
664 p.setAntiAlias(false);
666 final int width = getMeasuredWidth();
667 final int height = getMeasuredHeight();
669 for (int row = 0; row < 6; row++) {
670 int y = WEEK_GAP + row * (WEEK_GAP + mCellHeight) - 1;
671 canvas.drawLine(0, y, width, y, p);
673 for (int column = 1; column < 7; column++) {
674 int x = mBorder + column * (MONTH_DAY_GAP + mCellWidth) - 1;
675 canvas.drawLine(x, WEEK_GAP, x, height, p);
680 * Draw a single box onto the canvas.
681 * @param day The Julian day.
682 * @param weekNum The week number.
683 * @param row The row of the box (0-5).
684 * @param column The column of the box (0-6).
685 * @param canvas The canvas to draw on.
686 * @param p The paint used for drawing.
687 * @param r The rectangle used for each box.
688 * @param isLandscape Is the current orientation landscape.
690 private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p,
691 Rect r, boolean isLandscape) {
693 // Only draw the selection if we are in the press state or if we have
694 // moved the cursor with key input.
695 boolean drawSelection = false;
696 if (mSelectionMode != SELECTION_HIDDEN) {
697 drawSelection = mCursor.isSelected(row, column);
700 boolean withinCurrentMonth = mCursor.isWithinCurrentMonth(row, column);
701 boolean isToday = false;
702 int dayOfBox = mCursor.getDayAt(row, column);
703 if (dayOfBox == mToday.monthDay && mCursor.getYear() == mToday.year
704 && mCursor.getMonth() == mToday.month) {
708 int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight);
709 int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth);
713 r.right = x + mCellWidth;
714 r.bottom = y + mCellHeight;
717 // Adjust the left column, right column, and bottom row to leave
721 } else if (column == 6) {
722 r.right += mBorder + 2;
726 r.bottom = getMeasuredHeight();
730 // Draw the cell contents (excluding monthDay number)
731 if (!withinCurrentMonth) {
732 boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column);
734 // Adjust cell boundaries to compensate for the different border
740 p.setStyle(Style.FILL);
741 p.setColor(LIGHT_GRAY_BGCOLOR);
742 canvas.drawRect(r, p);
743 } else if (drawSelection) {
744 if (mSelectionMode == SELECTION_SELECTED) {
745 mBoxSelected.setBounds(r);
746 mBoxSelected.draw(canvas);
747 } else if (mSelectionMode == SELECTION_PRESSED) {
748 mBoxPressed.setBounds(r);
749 mBoxPressed.draw(canvas);
751 mBoxLongPressed.setBounds(r);
752 mBoxLongPressed.draw(canvas);
755 //Places events for that day
756 drawEvents(day, canvas, r, p, false /*draw bb background*/);
758 updateEventDetails(day);
761 // Today gets a different background
763 // We could cache this for a little bit more performance, but it's not on the
764 // performance radar...
765 Drawable background = mTodayBackground;
766 background.setBounds(r);
767 background.draw(canvas);
769 //Places events for that day
770 drawEvents(day, canvas, r, p, !isToday /*draw bb background*/);
774 if (mShowWeekNumbers && column == 0) {
776 p.setStyle(Paint.Style.FILL);
777 p.setColor(mMonthWeekBannerColor);
779 int bottom = r.bottom;
780 r.bottom = r.top + WEEK_BANNER_HEIGHT;
782 canvas.drawRect(r, p);
787 r.top = r.bottom - WEEK_BANNER_HEIGHT;
789 canvas.drawRect(r, p);
795 p.setColor(mMonthOtherMonthBannerColor);
796 p.setAntiAlias(true);
798 p.setTextSize(WEEK_TEXT_SIZE);
799 p.setTextAlign(Paint.Align.LEFT);
801 int textX = r.left + WEEK_TEXT_PADDING;
804 textY = r.top + WEEK_BANNER_HEIGHT - WEEK_TEXT_PADDING;
806 textY = r.bottom - WEEK_TEXT_PADDING;
809 canvas.drawText(String.valueOf(weekNum), textX, textY, p);
812 // Draw the monthDay number
813 p.setStyle(Paint.Style.FILL);
814 p.setAntiAlias(true);
816 p.setTextSize(MONTH_DAY_TEXT_SIZE);
818 if (!withinCurrentMonth) {
819 p.setColor(mMonthOtherMonthDayNumberColor);
821 if (isToday && !drawSelection) {
822 p.setColor(mMonthTodayNumberColor);
823 } else if (Utils.isSunday(column, mStartDay)) {
824 p.setColor(mMonthSundayColor);
825 } else if (Utils.isSaturday(column, mStartDay)) {
826 p.setColor(mMonthSaturdayColor);
828 p.setColor(mMonthDayNumberColor);
830 //bolds the day if there's an event that day
831 p.setFakeBoldText(eventDay[day-mFirstJulianDay]);
833 /*Drawing of day number is done here
834 *easy to find tags draw number draw day*/
835 p.setTextAlign(Paint.Align.CENTER);
837 int textX = r.left + (r.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH - r.left) / 2;
838 int textY = (int) (r.top + p.getTextSize() + TEXT_TOP_MARGIN); // bottom of text
839 canvas.drawText(String.valueOf(mCursor.getDayAt(row, column)), textX, textY, p);
842 ///Create and draw the event busybits for this day
843 private void drawEvents(int date, Canvas canvas, Rect rect, Paint p, boolean drawBg) {
844 // The top of the busybits section lines up with the top of the day number
845 int top = rect.top + TEXT_TOP_MARGIN + BUSY_BITS_MARGIN;
846 int left = rect.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH;
848 Style oldStyle = p.getStyle();
849 int oldColor = p.getColor();
851 ArrayList<Event> events = mEvents;
852 int numEvents = events.size();
853 EventGeometry geometry = mEventGeometry;
858 rf.right = left + BUSY_BITS_WIDTH;
859 rf.bottom = rect.bottom - BUSY_BITS_MARGIN;
862 p.setColor(LIGHT_GRAY_BGCOLOR);
863 p.setStyle(Style.FILL);
864 canvas.drawRect(rf, p);
867 for (int i = 0; i < numEvents; i++) {
868 Event event = events.get(i);
869 if (!geometry.computeEventRect(date, left, top, BUSY_BITS_WIDTH, event)) {
872 drawEventRect(rect, event, canvas, p);
877 // Draw busybits for a single event
878 private RectF drawEventRect(Rect rect, Event event, Canvas canvas, Paint p) {
880 p.setColor(BUSY_BITS_COLOR);
882 int left = rect.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH;
883 int bottom = rect.bottom - BUSY_BITS_MARGIN;
887 // Make sure we don't go below the bottom of the bb bar
888 rf.bottom = Math.min(event.bottom, bottom);
890 rf.right = left + BUSY_BITS_WIDTH;
892 canvas.drawRect(rf, p);
897 private boolean isFirstDayOfNextMonth(int row, int column) {
904 return mCursor.isWithinCurrentMonth(row, column);
907 private int getWeekOfYear(int row, int column, boolean isWithinCurrentMonth,
909 calendar.set(Calendar.DAY_OF_MONTH, mCursor.getDayAt(row, column));
910 if (isWithinCurrentMonth) {
911 calendar.set(Calendar.MONTH, mCursor.getMonth());
912 calendar.set(Calendar.YEAR, mCursor.getYear());
914 int month = mCursor.getMonth();
915 int year = mCursor.getYear();
933 calendar.set(Calendar.MONTH, month);
934 calendar.set(Calendar.YEAR, year);
937 return calendar.get(Calendar.WEEK_OF_YEAR);
940 void setDetailedView(String detailedView) {
941 mDetailedView = detailedView;
944 void setSelectedTime(Time time) {
945 // Save the selected time so that we can restore it later when we switch views.
946 mSavedTime.set(time);
948 mViewCalendar.set(time);
949 mViewCalendar.monthDay = 1;
950 long millis = mViewCalendar.normalize(true /* ignore DST */);
951 mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
952 mViewCalendar.set(time);
954 mCursor = new DayOfMonthCursor(time.year, time.month, time.monthDay,
955 mCursor.getWeekStartDay());
957 mRedrawScreen = true;
961 public long getSelectedTimeInMillis() {
962 Time time = mTempTime;
963 time.set(mViewCalendar);
965 time.month += mCursor.getSelectedMonthOffset();
966 time.monthDay = mCursor.getSelectedDayOfMonth();
968 // Restore the saved hour:minute:second offset from when we entered
970 time.second = mSavedTime.second;
971 time.minute = mSavedTime.minute;
972 time.hour = mSavedTime.hour;
973 return time.normalize(true);
977 return mViewCalendar;
980 public int getSelectionMode() {
981 return mSelectionMode;
984 public void setSelectionMode(int selectionMode) {
985 mSelectionMode = selectionMode;
988 private void drawingCalc(int width, int height) {
989 mCellHeight = (height - (6 * WEEK_GAP)) / 6;
991 .setHourHeight((mCellHeight - BUSY_BITS_MARGIN * 2 - TEXT_TOP_MARGIN) / 24.0f);
992 mCellWidth = (width - (6 * MONTH_DAY_GAP)) / 7;
993 mBorder = (width - 6 * (mCellWidth + MONTH_DAY_GAP) - mCellWidth) / 2;
997 mPopup.setWidth(width - 20);
998 mPopup.setHeight(POPUP_HEIGHT);
1001 if (((mBitmap == null)
1002 || mBitmap.isRecycled()
1003 || (mBitmap.getHeight() != height)
1004 || (mBitmap.getWidth() != width))
1005 && (width > 0) && (height > 0)) {
1006 if (mBitmap != null) {
1009 mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1010 mCanvas = new Canvas(mBitmap);
1013 mBitmapRect.top = 0;
1014 mBitmapRect.bottom = height;
1015 mBitmapRect.left = 0;
1016 mBitmapRect.right = width;
1019 private void updateEventDetails(int date) {
1024 getHandler().removeCallbacks(mDismissPopup);
1025 ArrayList<Event> events = mEvents;
1026 int numEvents = events.size();
1027 if (numEvents == 0) {
1033 for (int i = 0; i < numEvents; i++) {
1034 Event event = events.get(i);
1036 if (event.startDay > date || event.endDay < date) {
1040 // If we have all the event that we can display, then just count
1042 if (eventIndex >= 4) {
1048 boolean showEndTime = false;
1050 int numDays = event.endDay - event.startDay;
1052 flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
1053 | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
1056 flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
1057 | DateUtils.FORMAT_ABBREV_ALL;
1060 flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
1061 if (DateFormat.is24HourFormat(mParentActivity)) {
1062 flags |= DateUtils.FORMAT_24HOUR;
1068 timeRange = DateUtils.formatDateRange(mParentActivity,
1069 event.startMillis, event.endMillis, flags);
1071 timeRange = DateUtils.formatDateRange(mParentActivity,
1072 event.startMillis, event.startMillis, flags);
1075 TextView timeView = null;
1076 TextView titleView = null;
1077 switch (eventIndex) {
1079 timeView = (TextView) mPopupView.findViewById(R.id.time0);
1080 titleView = (TextView) mPopupView.findViewById(R.id.event_title0);
1083 timeView = (TextView) mPopupView.findViewById(R.id.time1);
1084 titleView = (TextView) mPopupView.findViewById(R.id.event_title1);
1087 timeView = (TextView) mPopupView.findViewById(R.id.time2);
1088 titleView = (TextView) mPopupView.findViewById(R.id.event_title2);
1091 timeView = (TextView) mPopupView.findViewById(R.id.time3);
1092 titleView = (TextView) mPopupView.findViewById(R.id.event_title3);
1096 timeView.setText(timeRange);
1097 titleView.setText(event.title);
1100 if (eventIndex == 0) {
1101 // We didn't find any events for this day
1106 // Hide the items that have no event information
1108 switch (eventIndex) {
1110 view = mPopupView.findViewById(R.id.item_layout1);
1111 view.setVisibility(View.GONE);
1112 view = mPopupView.findViewById(R.id.item_layout2);
1113 view.setVisibility(View.GONE);
1114 view = mPopupView.findViewById(R.id.item_layout3);
1115 view.setVisibility(View.GONE);
1116 view = mPopupView.findViewById(R.id.plus_more);
1117 view.setVisibility(View.GONE);
1120 view = mPopupView.findViewById(R.id.item_layout1);
1121 view.setVisibility(View.VISIBLE);
1122 view = mPopupView.findViewById(R.id.item_layout2);
1123 view.setVisibility(View.GONE);
1124 view = mPopupView.findViewById(R.id.item_layout3);
1125 view.setVisibility(View.GONE);
1126 view = mPopupView.findViewById(R.id.plus_more);
1127 view.setVisibility(View.GONE);
1130 view = mPopupView.findViewById(R.id.item_layout1);
1131 view.setVisibility(View.VISIBLE);
1132 view = mPopupView.findViewById(R.id.item_layout2);
1133 view.setVisibility(View.VISIBLE);
1134 view = mPopupView.findViewById(R.id.item_layout3);
1135 view.setVisibility(View.GONE);
1136 view = mPopupView.findViewById(R.id.plus_more);
1137 view.setVisibility(View.GONE);
1140 view = mPopupView.findViewById(R.id.item_layout1);
1141 view.setVisibility(View.VISIBLE);
1142 view = mPopupView.findViewById(R.id.item_layout2);
1143 view.setVisibility(View.VISIBLE);
1144 view = mPopupView.findViewById(R.id.item_layout3);
1145 view.setVisibility(View.VISIBLE);
1146 view = mPopupView.findViewById(R.id.plus_more);
1147 view.setVisibility(View.GONE);
1150 view = mPopupView.findViewById(R.id.item_layout1);
1151 view.setVisibility(View.VISIBLE);
1152 view = mPopupView.findViewById(R.id.item_layout2);
1153 view.setVisibility(View.VISIBLE);
1154 view = mPopupView.findViewById(R.id.item_layout3);
1155 view.setVisibility(View.VISIBLE);
1156 TextView tv = (TextView) mPopupView.findViewById(R.id.plus_more);
1157 tv.setVisibility(View.VISIBLE);
1158 String format = mResources.getString(R.string.plus_N_more);
1159 String plusMore = String.format(format, eventIndex - 4);
1160 tv.setText(plusMore);
1164 if (eventIndex > 5) {
1167 int popupHeight = 20 * eventIndex + 15;
1168 mPopup.setHeight(popupHeight);
1170 if (mPreviousPopupHeight != popupHeight) {
1171 mPreviousPopupHeight = popupHeight;
1174 mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
1175 postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
1179 public boolean onKeyUp(int keyCode, KeyEvent event) {
1180 long duration = event.getEventTime() - event.getDownTime();
1183 case KeyEvent.KEYCODE_DPAD_CENTER:
1184 if (mSelectionMode == SELECTION_HIDDEN) {
1185 // Don't do anything unless the selection is visible.
1189 if (mSelectionMode == SELECTION_PRESSED) {
1190 // This was the first press when there was nothing selected.
1191 // Change the selection from the "pressed" state to the
1192 // the "selected" state. We treat short-press and
1193 // long-press the same here because nothing was selected.
1194 mSelectionMode = SELECTION_SELECTED;
1195 mRedrawScreen = true;
1200 // Check the duration to determine if this was a short press
1201 if (duration < ViewConfiguration.getLongPressTimeout()) {
1202 long millis = getSelectedTimeInMillis();
1203 Utils.startActivity(getContext(), mDetailedView, millis);
1205 mSelectionMode = SELECTION_LONGPRESS;
1206 mRedrawScreen = true;
1211 return super.onKeyUp(keyCode, event);
1215 public boolean onKeyDown(int keyCode, KeyEvent event) {
1216 if (mSelectionMode == SELECTION_HIDDEN) {
1217 if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
1218 || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
1219 || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
1220 // Display the selection box but don't move or select it
1221 // on this key press.
1222 mSelectionMode = SELECTION_SELECTED;
1223 mRedrawScreen = true;
1226 } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
1227 // Display the selection box but don't select it
1228 // on this key press.
1229 mSelectionMode = SELECTION_PRESSED;
1230 mRedrawScreen = true;
1236 mSelectionMode = SELECTION_SELECTED;
1237 boolean redraw = false;
1241 case KeyEvent.KEYCODE_ENTER:
1242 long millis = getSelectedTimeInMillis();
1243 Utils.startActivity(getContext(), mDetailedView, millis);
1245 case KeyEvent.KEYCODE_DPAD_UP:
1247 other = mOtherViewCalendar;
1248 other.set(mViewCalendar);
1250 other.monthDay = mCursor.getSelectedDayOfMonth();
1252 // restore the calendar cursor for the animation
1258 case KeyEvent.KEYCODE_DPAD_DOWN:
1259 if (mCursor.down()) {
1260 other = mOtherViewCalendar;
1261 other.set(mViewCalendar);
1263 other.monthDay = mCursor.getSelectedDayOfMonth();
1265 // restore the calendar cursor for the animation
1271 case KeyEvent.KEYCODE_DPAD_LEFT:
1272 if (mCursor.left()) {
1273 other = mOtherViewCalendar;
1274 other.set(mViewCalendar);
1276 other.monthDay = mCursor.getSelectedDayOfMonth();
1278 // restore the calendar cursor for the animation
1284 case KeyEvent.KEYCODE_DPAD_RIGHT:
1285 if (mCursor.right()) {
1286 other = mOtherViewCalendar;
1287 other.set(mViewCalendar);
1289 other.monthDay = mCursor.getSelectedDayOfMonth();
1291 // restore the calendar cursor for the animation
1298 if (other != null) {
1299 other.normalize(true /* ignore DST */);
1300 mNavigator.goTo(other, true);
1301 } else if (redraw) {
1302 mRedrawScreen = true;
1309 class DismissPopup implements Runnable {
1315 // This is called when the activity is paused so that the popup can
1317 void dismissPopup() {
1322 // Protect against null-pointer exceptions
1323 if (mPopup != null) {
1327 Handler handler = getHandler();
1328 if (handler != null) {
1329 handler.removeCallbacks(mDismissPopup);