OSDN Git Service

77e78fa3d07ff0b581ceed926a3f9a6f36739544
[android-x86/packages-apps-Calendar.git] / src / com / android / calendar / MonthView.java
1 /*
2  * Copyright (C) 2006 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 com.android.calendar;
18
19 import static android.provider.Calendar.EVENT_BEGIN_TIME;
20 import static android.provider.Calendar.EVENT_END_TIME;
21
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;
52
53 import java.util.ArrayList;
54 import java.util.Calendar;
55
56 public class MonthView extends View implements View.OnCreateContextMenuListener {
57
58     private static final boolean PROFILE_LOAD_TIME = false;
59
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;
73
74     private static int HORIZONTAL_FLING_THRESHOLD = 50;
75
76     private int mCellHeight;
77     private int mBorder;
78     private boolean mLaunchDayView;
79
80     private GestureDetector mGestureDetector;
81
82     private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW;
83
84     private Time mToday;
85     private Time mViewCalendar;
86     private Time mSavedTime = new Time();   // the time when we entered this view
87
88     // This Time object is used to set the time for the other Month view.
89     private Time mOtherViewCalendar = new Time();
90
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();
94
95     private DayOfMonthCursor mCursor;
96
97     private Drawable mBoxSelected;
98     private Drawable mBoxPressed;
99     private Drawable mBoxLongPressed;
100     private Drawable mEventDot;
101     private int mCellWidth;
102
103     private Resources mResources;
104     private MonthActivity mParentActivity;
105     private Navigator mNavigator;
106     private final EventGeometry mEventGeometry;
107
108     // Pre-allocate and reuse
109     private Rect mRect = new Rect();
110
111     //An array of which days have events for quick reference
112     private boolean[] eventDay = new boolean[31];
113
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();
120
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;
127
128     // These booleans disable features that were taken out of the spec.
129     private boolean mShowWeekNumbers = false;
130     private boolean mShowToast = false;
131
132     // Bitmap caches.
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);
137
138     private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
139
140     /**
141      * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
142      */
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;
147
148     // Modulo used to pack (width,height) into a unique integer
149     private static final int MODULO_SHIFT = 16;
150
151     private int mSelectionMode = SELECTION_HIDDEN;
152
153     /**
154      * The first Julian day of the current month.
155      */
156     private int mFirstJulianDay;
157
158     private int mStartDay;
159
160     private final EventLoader mEventLoader;
161
162     private ArrayList<Event> mEvents = new ArrayList<Event>();
163
164     private Drawable mTodayBackground;
165     private Drawable mDayBackground;
166
167     // Cached colors
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;
176
177     public MonthView(MonthActivity activity, Navigator navigator) {
178         super(activity);
179         if (mScale == 0) {
180             mScale = getContext().getResources().getDisplayMetrics().density;
181            if (mScale != 1) {
182                     WEEK_GAP *= mScale;
183                     MONTH_DAY_GAP *= mScale;
184                     HOUR_GAP *= mScale;
185
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;
195                 }
196             }
197
198         mEventLoader = activity.mEventLoader;
199         mNavigator = navigator;
200         mEventGeometry = new EventGeometry();
201         mEventGeometry.setMinEventHeight(1.0f);
202         mEventGeometry.setHourGap(HOUR_GAP);
203         init(activity);
204     }
205
206     private void init(MonthActivity activity) {
207         setFocusable(true);
208         setClickable(true);
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);
219
220         mCursor = new DayOfMonthCursor(mViewCalendar.year,  mViewCalendar.month,
221                 mViewCalendar.monthDay, mParentActivity.getStartDay());
222         mToday = new Time();
223         mToday.set(System.currentTimeMillis());
224
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);
229
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);
233
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);
244
245         if (mShowToast) {
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));
256             ta.recycle();
257         }
258
259         mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
260             @Override
261             public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
262                     float velocityY) {
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;
268
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) {
272                     return false;
273                 }
274
275                 // Switch to a different month
276                 Time time = mOtherViewCalendar;
277                 time.set(mViewCalendar);
278                 if (velocityY < 0) {
279                     time.month += 1;
280                 } else {
281                     time.month -= 1;
282                 }
283                 time.normalize(true);
284                 mParentActivity.goTo(time, true);
285
286                 return true;
287             }
288
289             @Override
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;
294                 return true;
295             }
296
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);
302                 if (row > 5) {
303                     row = 5;
304                 }
305                 if (col > 6) {
306                     col = 6;
307                 }
308
309                 // Highlight the selected day.
310                 mCursor.setSelectedRowColumn(row, col);
311             }
312
313             @Override
314             public void onShowPress(MotionEvent e) {
315                 // Highlight the selected day.
316                 setSelectedCell(e);
317                 mSelectionMode = SELECTION_PRESSED;
318                 mRedrawScreen = true;
319                 invalidate();
320             }
321
322             @Override
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;
331                     invalidate();
332                     performLongClick();
333                 }
334             }
335
336             @Override
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;
343
344                 if (mSelectionMode != SELECTION_HIDDEN) {
345                     mSelectionMode = SELECTION_HIDDEN;
346                     mRedrawScreen = true;
347                     invalidate();
348                 }
349                 return true;
350             }
351
352             @Override
353             public boolean onSingleTapUp(MotionEvent e) {
354                 if (mLaunchDayView) {
355                     setSelectedCell(e);
356                     mSelectionMode = SELECTION_SELECTED;
357                     mRedrawScreen = true;
358                     invalidate();
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);
364                 }
365
366                 return true;
367             }
368         });
369     }
370
371     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
372         MenuItem item;
373
374         final long startMillis = getSelectedTimeInMillis();
375         final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
376                 | DateUtils.FORMAT_ABBREV_MONTH;
377
378         final String title = DateUtils.formatDateTime(mParentActivity, startMillis, flags);
379         menu.setHeaderTitle(title);
380
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');
385
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');
390
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');
395     }
396
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);
403                     break;
404                 }
405                 case MenuHelper.MENU_AGENDA: {
406                     long startMillis = getSelectedTimeInMillis();
407                     Utils.startActivity(mParentActivity, AgendaActivity.class.getName(), startMillis);
408                     break;
409                 }
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);
418                     break;
419                 }
420                 default: {
421                     return false;
422                 }
423             }
424             return true;
425         }
426     }
427
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;
433         monthStart.hour = 0;
434         monthStart.minute = 0;
435         monthStart.second = 0;
436         long millis = monthStart.normalize(true /* ignore isDst */);
437         int startDay = Time.getJulianDay(millis, monthStart.gmtoff);
438
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();
444         } else {
445             // To avoid a compiler error that this variable might not be initialized.
446             startMillis = 0;
447         }
448
449         mEventLoader.loadEventDaysInBackground(startDay, EVENT_NUM_DAYS, eventDay,
450                 new Runnable() {
451             public void run() {
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);
457                 }
458                 //Refresh the screen when we're done and stop the spinner
459                 mRedrawScreen = true;
460                 mParentActivity.stopProgressSpinner();
461                 invalidate();
462             }
463         });
464     }
465
466     void animationStarted() {
467         mAnimating = true;
468     }
469
470     void animationFinished() {
471         mAnimating = false;
472         mRedrawScreen = true;
473         invalidate();
474     }
475
476     @Override
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...
480         clearBitmapCache();
481     }
482
483     @Override
484     protected void onDetachedFromWindow() {
485         super.onDetachedFromWindow();
486         // No need to hang onto the bitmaps...
487         clearBitmapCache();
488         if (mBitmap != null) {
489             mBitmap.recycle();
490         }
491     }
492
493     @Override
494     protected void onDraw(Canvas canvas) {
495         if (mRedrawScreen) {
496             if (mCanvas == null) {
497                 drawingCalc(getWidth(), getHeight());
498             }
499
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;
507             }
508         }
509
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);
513         }
514     }
515
516     private void doDraw(Canvas canvas) {
517         boolean isLandscape = getResources().getConfiguration().orientation
518                 == Configuration.ORIENTATION_LANDSCAPE;
519
520         Paint p = new Paint();
521         Rect r = mRect;
522         int columnDay1 = mCursor.getColumnOf(1);
523
524         // Get the Julian day for the date at row 0, column 0.
525         int day = mFirstJulianDay - columnDay1;
526
527         int weekNum = 0;
528         Calendar calendar = null;
529         if (mShowWeekNumbers) {
530             calendar = Calendar.getInstance();
531             boolean noPrevMonth = (columnDay1 == 0);
532
533             // Compute the week number for the first row.
534             weekNum = getWeekOfYear(0, 0, noPrevMonth, calendar);
535         }
536
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);
540                 day += 1;
541             }
542
543             if (mShowWeekNumbers) {
544                 weekNum += 1;
545                 if (weekNum >= 53) {
546                     boolean inCurrentMonth = (day - mFirstJulianDay < 31);
547                     weekNum = getWeekOfYear(row + 1, 0, inCurrentMonth, calendar);
548                 }
549             }
550         }
551
552         drawGrid(canvas, p);
553     }
554
555     @Override
556     public boolean onTouchEvent(MotionEvent event) {
557         if (mGestureDetector.onTouchEvent(event)) {
558             return true;
559         }
560
561         return super.onTouchEvent(event);
562     }
563
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);
567         if (column > 6) {
568             column = 6;
569         }
570
571         DayOfMonthCursor c = mCursor;
572         Time time = mTempTime;
573         time.set(mViewCalendar);
574
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);
582     }
583
584     /**
585      * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect.
586      *
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
591      */
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());
595
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);
600         return bitmap;
601     }
602
603     /**
604      * Clears the bitmap cache. Generally only needed when the screen size changed.
605      */
606     private void clearBitmapCache() {
607         recycleAndClearBitmapCache(mDayBitmapCache);
608     }
609
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();
614         }
615         bitmapCache.clear();
616
617     }
618
619     /**
620      * Draw the grid lines for the calendar
621      * @param canvas The canvas to draw on.
622      * @param p The paint used for drawing.
623      */
624     private void drawGrid(Canvas canvas, Paint p) {
625         p.setColor(mMonthOtherMonthColor);
626         p.setAntiAlias(false);
627
628         final int width = getMeasuredWidth();
629         final int height = getMeasuredHeight();
630
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);
634         }
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);
638         }
639     }
640
641     /**
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.
651      */
652     private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p,
653             Rect r, boolean isLandscape) {
654
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);
660         }
661
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) {
667             isToday = true;
668         }
669
670         int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight);
671         int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth);
672
673         r.left = x;
674         r.top = y;
675         r.right = x + mCellWidth;
676         r.bottom = y + mCellHeight;
677
678
679         // Adjust the left column, right column, and bottom row to leave
680         // no border.
681         if (column == 0) {
682             r.left = -1;
683         } else if (column == 6) {
684             r.right += mBorder + 2;
685         }
686
687         if (row == 5) {
688             r.bottom = getMeasuredHeight();
689         }
690
691         // Draw the cell contents (excluding monthDay number)
692         if (!withinCurrentMonth) {
693             boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column);
694
695             // Adjust cell boundaries to compensate for the different border
696             // style.
697             r.top--;
698             if (column != 0) {
699                 r.left--;
700             }
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);
708             } else {
709                 mBoxLongPressed.setBounds(r);
710                 mBoxLongPressed.draw(canvas);
711             }
712
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);
717             }
718             if (!mAnimating) {
719                 updateEventDetails(day);
720             }
721         } else {
722             // Today gets a different background
723             if (isToday) {
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);
729             } else {
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);
739                 }
740                 canvas.drawBitmap(bitmap, r.left, r.top, p);
741             }
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);
746             }
747         }
748
749         // Draw week number
750         if (mShowWeekNumbers && column == 0) {
751             // Draw the banner
752             p.setStyle(Paint.Style.FILL);
753             p.setColor(mMonthWeekBannerColor);
754             if (isLandscape) {
755                 int bottom = r.bottom;
756                 r.bottom = r.top + WEEK_BANNER_HEIGHT;
757                 r.left++;
758                 canvas.drawRect(r, p);
759                 r.bottom = bottom;
760                 r.left--;
761             } else {
762                 int top = r.top;
763                 r.top = r.bottom - WEEK_BANNER_HEIGHT;
764                 r.left++;
765                 canvas.drawRect(r, p);
766                 r.top = top;
767                 r.left--;
768             }
769
770             // Draw the number
771             p.setColor(mMonthOtherMonthBannerColor);
772             p.setAntiAlias(true);
773             p.setTypeface(null);
774             p.setTextSize(WEEK_TEXT_SIZE);
775             p.setTextAlign(Paint.Align.LEFT);
776
777             int textX = r.left + WEEK_TEXT_PADDING;
778             int textY;
779             if (isLandscape) {
780                 textY = r.top + WEEK_BANNER_HEIGHT - WEEK_TEXT_PADDING;
781             } else {
782                 textY = r.bottom - WEEK_TEXT_PADDING;
783             }
784
785             canvas.drawText(String.valueOf(weekNum), textX, textY, p);
786         }
787
788         // Draw the monthDay number
789         p.setStyle(Paint.Style.FILL);
790         p.setAntiAlias(true);
791         p.setTypeface(null);
792         p.setTextSize(MONTH_DAY_TEXT_SIZE);
793
794         if (!withinCurrentMonth) {
795             p.setColor(mMonthOtherMonthDayNumberColor);
796         } else {
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);
803             } else {
804                 p.setColor(mMonthDayNumberColor);
805             }
806             //bolds the day if there's an event that day
807             p.setFakeBoldText(eventDay[day-mFirstJulianDay]);
808         }
809         /*Drawing of day number is done here
810          *easy to find tags draw number draw day*/
811         p.setTextAlign(Paint.Align.CENTER);
812         int right = r.right;
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);
816     }
817
818     ///Create and draw an event dot for  this day
819     private void drawEvents(int date, Canvas canvas, Rect rect, Paint p)
820     {
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);
825
826         canvas.drawBitmap(bitmap, left, top, p);
827     }
828
829     private boolean isFirstDayOfNextMonth(int row, int column) {
830         if (column == 0) {
831             column = 6;
832             row--;
833         } else {
834             column--;
835         }
836         return mCursor.isWithinCurrentMonth(row, column);
837     }
838
839     private int getWeekOfYear(int row, int column, boolean isWithinCurrentMonth,
840             Calendar calendar) {
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());
845         } else {
846             int month = mCursor.getMonth();
847             int year = mCursor.getYear();
848             if (row < 2) {
849                 // Previous month
850                 if (month == 0) {
851                     year--;
852                     month = 11;
853                 } else {
854                     month--;
855                 }
856             } else {
857                 // Next month
858                 if (month == 11) {
859                     year++;
860                     month = 0;
861                 } else {
862                     month++;
863                 }
864             }
865             calendar.set(Calendar.MONTH, month);
866             calendar.set(Calendar.YEAR, year);
867         }
868
869         return calendar.get(Calendar.WEEK_OF_YEAR);
870     }
871
872     void setDetailedView(String detailedView) {
873         mDetailedView = detailedView;
874     }
875
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);
879
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);
885
886         mCursor = new DayOfMonthCursor(time.year, time.month, time.monthDay,
887                 mCursor.getWeekStartDay());
888
889         mRedrawScreen = true;
890         invalidate();
891     }
892
893     public long getSelectedTimeInMillis() {
894         Time time = mTempTime;
895         time.set(mViewCalendar);
896
897         time.month += mCursor.getSelectedMonthOffset();
898         time.monthDay = mCursor.getSelectedDayOfMonth();
899
900         // Restore the saved hour:minute:second offset from when we entered
901         // this view.
902         time.second = mSavedTime.second;
903         time.minute = mSavedTime.minute;
904         time.hour = mSavedTime.hour;
905         return time.normalize(true);
906     }
907
908     Time getTime() {
909         return mViewCalendar;
910     }
911
912     public int getSelectionMode() {
913         return mSelectionMode;
914     }
915
916     public void setSelectionMode(int selectionMode) {
917         mSelectionMode = selectionMode;
918     }
919
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;
925
926         if (mShowToast) {
927             mPopup.dismiss();
928             mPopup.setWidth(width - 20);
929             mPopup.setHeight(POPUP_HEIGHT);
930         }
931
932         if (((mBitmap == null)
933                     || mBitmap.isRecycled()
934                     || (mBitmap.getHeight() != height)
935                     || (mBitmap.getWidth() != width))
936                 && (width > 0) && (height > 0)) {
937             if (mBitmap != null) {
938                 mBitmap.recycle();
939             }
940             mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
941             mCanvas = new Canvas(mBitmap);
942         }
943
944         mBitmapRect.top = 0;
945         mBitmapRect.bottom = height;
946         mBitmapRect.left = 0;
947         mBitmapRect.right = width;
948     }
949
950     private void updateEventDetails(int date) {
951         if (!mShowToast) {
952             return;
953         }
954
955         getHandler().removeCallbacks(mDismissPopup);
956         ArrayList<Event> events = mEvents;
957         int numEvents = events.size();
958         if (numEvents == 0) {
959             mPopup.dismiss();
960             return;
961         }
962
963         int eventIndex = 0;
964         for (int i = 0; i < numEvents; i++) {
965             Event event = events.get(i);
966
967             if (event.startDay > date || event.endDay < date) {
968                 continue;
969             }
970
971             // If we have all the event that we can display, then just count
972             // the extra ones.
973             if (eventIndex >= 4) {
974                 eventIndex += 1;
975                 continue;
976             }
977
978             int flags;
979             boolean showEndTime = false;
980             if (event.allDay) {
981                 int numDays = event.endDay - event.startDay;
982                 if (numDays == 0) {
983                     flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
984                             | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
985                 } else {
986                     showEndTime = true;
987                     flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
988                             | DateUtils.FORMAT_ABBREV_ALL;
989                 }
990             } else {
991                 flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
992                 if (DateFormat.is24HourFormat(mParentActivity)) {
993                     flags |= DateUtils.FORMAT_24HOUR;
994                 }
995             }
996
997             String timeRange;
998             if (showEndTime) {
999                 timeRange = DateUtils.formatDateRange(mParentActivity,
1000                         event.startMillis, event.endMillis, flags);
1001             } else {
1002                 timeRange = DateUtils.formatDateRange(mParentActivity,
1003                         event.startMillis, event.startMillis, flags);
1004             }
1005
1006             TextView timeView = null;
1007             TextView titleView = null;
1008             switch (eventIndex) {
1009                 case 0:
1010                     timeView = (TextView) mPopupView.findViewById(R.id.time0);
1011                     titleView = (TextView) mPopupView.findViewById(R.id.event_title0);
1012                     break;
1013                 case 1:
1014                     timeView = (TextView) mPopupView.findViewById(R.id.time1);
1015                     titleView = (TextView) mPopupView.findViewById(R.id.event_title1);
1016                     break;
1017                 case 2:
1018                     timeView = (TextView) mPopupView.findViewById(R.id.time2);
1019                     titleView = (TextView) mPopupView.findViewById(R.id.event_title2);
1020                     break;
1021                 case 3:
1022                     timeView = (TextView) mPopupView.findViewById(R.id.time3);
1023                     titleView = (TextView) mPopupView.findViewById(R.id.event_title3);
1024                     break;
1025             }
1026
1027             timeView.setText(timeRange);
1028             titleView.setText(event.title);
1029             eventIndex += 1;
1030         }
1031         if (eventIndex == 0) {
1032             // We didn't find any events for this day
1033             mPopup.dismiss();
1034             return;
1035         }
1036
1037         // Hide the items that have no event information
1038         View view;
1039         switch (eventIndex) {
1040             case 1:
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);
1049                 break;
1050             case 2:
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);
1059                 break;
1060             case 3:
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);
1069                 break;
1070             case 4:
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);
1079                 break;
1080             default:
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);
1092                 break;
1093         }
1094
1095         if (eventIndex > 5) {
1096             eventIndex = 5;
1097         }
1098         int popupHeight = 20 * eventIndex + 15;
1099         mPopup.setHeight(popupHeight);
1100
1101         if (mPreviousPopupHeight != popupHeight) {
1102             mPreviousPopupHeight = popupHeight;
1103             mPopup.dismiss();
1104         }
1105         mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
1106         postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
1107     }
1108
1109     @Override
1110     public boolean onKeyUp(int keyCode, KeyEvent event) {
1111         long duration = event.getEventTime() - event.getDownTime();
1112
1113         switch (keyCode) {
1114         case KeyEvent.KEYCODE_DPAD_CENTER:
1115             if (mSelectionMode == SELECTION_HIDDEN) {
1116                 // Don't do anything unless the selection is visible.
1117                 break;
1118             }
1119
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;
1127                 invalidate();
1128                 break;
1129             }
1130
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);
1135             } else {
1136                 mSelectionMode = SELECTION_LONGPRESS;
1137                 mRedrawScreen = true;
1138                 invalidate();
1139                 performLongClick();
1140             }
1141         }
1142         return super.onKeyUp(keyCode, event);
1143     }
1144
1145     @Override
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;
1155                 invalidate();
1156                 return 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;
1162                 invalidate();
1163                 return true;
1164             }
1165         }
1166
1167         mSelectionMode = SELECTION_SELECTED;
1168         boolean redraw = false;
1169         Time other = null;
1170
1171         switch (keyCode) {
1172         case KeyEvent.KEYCODE_ENTER:
1173             long millis = getSelectedTimeInMillis();
1174             Utils.startActivity(getContext(), mDetailedView, millis);
1175             return true;
1176         case KeyEvent.KEYCODE_DPAD_UP:
1177             if (mCursor.up()) {
1178                 other = mOtherViewCalendar;
1179                 other.set(mViewCalendar);
1180                 other.month -= 1;
1181                 other.monthDay = mCursor.getSelectedDayOfMonth();
1182
1183                 // restore the calendar cursor for the animation
1184                 mCursor.down();
1185             }
1186             redraw = true;
1187             break;
1188
1189         case KeyEvent.KEYCODE_DPAD_DOWN:
1190             if (mCursor.down()) {
1191                 other = mOtherViewCalendar;
1192                 other.set(mViewCalendar);
1193                 other.month += 1;
1194                 other.monthDay = mCursor.getSelectedDayOfMonth();
1195
1196                 // restore the calendar cursor for the animation
1197                 mCursor.up();
1198             }
1199             redraw = true;
1200             break;
1201
1202         case KeyEvent.KEYCODE_DPAD_LEFT:
1203             if (mCursor.left()) {
1204                 other = mOtherViewCalendar;
1205                 other.set(mViewCalendar);
1206                 other.month -= 1;
1207                 other.monthDay = mCursor.getSelectedDayOfMonth();
1208
1209                 // restore the calendar cursor for the animation
1210                 mCursor.right();
1211             }
1212             redraw = true;
1213             break;
1214
1215         case KeyEvent.KEYCODE_DPAD_RIGHT:
1216             if (mCursor.right()) {
1217                 other = mOtherViewCalendar;
1218                 other.set(mViewCalendar);
1219                 other.month += 1;
1220                 other.monthDay = mCursor.getSelectedDayOfMonth();
1221
1222                 // restore the calendar cursor for the animation
1223                 mCursor.left();
1224             }
1225             redraw = true;
1226             break;
1227         }
1228
1229         if (other != null) {
1230             other.normalize(true /* ignore DST */);
1231             mNavigator.goTo(other, true);
1232         } else if (redraw) {
1233             mRedrawScreen = true;
1234             invalidate();
1235         }
1236
1237         return redraw;
1238     }
1239
1240     class DismissPopup implements Runnable {
1241         public void run() {
1242             mPopup.dismiss();
1243         }
1244     }
1245
1246     // This is called when the activity is paused so that the popup can
1247     // be dismissed.
1248     void dismissPopup() {
1249         if (!mShowToast) {
1250             return;
1251         }
1252
1253         // Protect against null-pointer exceptions
1254         if (mPopup != null) {
1255             mPopup.dismiss();
1256         }
1257
1258         Handler handler = getHandler();
1259         if (handler != null) {
1260             handler.removeCallbacks(mDismissPopup);
1261         }
1262     }
1263 }