OSDN Git Service

c6aace3bd316696d24f4455d187220bdaf88fab8
[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.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;
56
57 import java.util.ArrayList;
58 import java.util.Calendar;
59
60 public class MonthView extends View implements View.OnCreateContextMenuListener {
61
62     private static final boolean PROFILE_LOAD_TIME = false;
63
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;
81
82     private static int HORIZONTAL_FLING_THRESHOLD = 50;
83
84     private static final int BUSY_BITS_COLOR = 0xFF6090F0;
85     private static final int LIGHT_GRAY_BGCOLOR = 0xFFDDDDDD;
86
87     private int mCellHeight;
88     private int mBorder;
89     private boolean mLaunchDayView;
90
91     private GestureDetector mGestureDetector;
92
93     private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW;
94
95     private Time mToday;
96     private Time mViewCalendar;
97     private Time mSavedTime = new Time();   // the time when we entered this view
98
99     // This Time object is used to set the time for the other Month view.
100     private Time mOtherViewCalendar = new Time();
101
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();
105
106     private DayOfMonthCursor mCursor;
107
108     private Drawable mBoxSelected;
109     private Drawable mBoxPressed;
110     private Drawable mBoxLongPressed;
111     private Drawable mEventDot;
112     private int mCellWidth;
113
114     private Resources mResources;
115     private MonthActivity mParentActivity;
116     private Navigator mNavigator;
117     private final EventGeometry mEventGeometry;
118
119     // Pre-allocate and reuse
120     private Rect mRect = new Rect();
121
122     //An array of which days have events for quick reference
123     private boolean[] eventDay = new boolean[31];
124
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();
131
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;
139
140     // These booleans disable features that were taken out of the spec.
141     private boolean mShowWeekNumbers = false;
142     private boolean mShowToast = false;
143
144     // Bitmap caches.
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);
149
150     private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
151
152     /**
153      * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
154      */
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;
159
160     // Modulo used to pack (width,height) into a unique integer
161     private static final int MODULO_SHIFT = 16;
162
163     private int mSelectionMode = SELECTION_HIDDEN;
164
165     /**
166      * The first Julian day of the current month.
167      */
168     private int mFirstJulianDay;
169
170     private int mStartDay;
171
172     private final EventLoader mEventLoader;
173
174     private ArrayList<Event> mEvents = new ArrayList<Event>();
175
176     private Drawable mTodayBackground;
177
178     // Cached colors
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;
187
188     public MonthView(MonthActivity activity, Navigator navigator) {
189         super(activity);
190         if (mScale == 0) {
191             mScale = getContext().getResources().getDisplayMetrics().density;
192            if (mScale != 1) {
193                     WEEK_GAP *= mScale;
194                     MONTH_DAY_GAP *= mScale;
195                     HOUR_GAP *= mScale;
196
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;
210                 }
211             }
212
213         mEventLoader = activity.mEventLoader;
214         mNavigator = navigator;
215         mEventGeometry = new EventGeometry();
216         mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
217         mEventGeometry.setHourGap(HOUR_GAP);
218         init(activity);
219     }
220
221     private void init(MonthActivity activity) {
222         setFocusable(true);
223         setClickable(true);
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);
234
235         mCursor = new DayOfMonthCursor(mViewCalendar.year,  mViewCalendar.month,
236                 mViewCalendar.monthDay, mParentActivity.getStartDay());
237         mToday = new Time();
238         mToday.set(System.currentTimeMillis());
239
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);
244
245         mEventDot = mResources.getDrawable(R.drawable.event_dot);
246         mTodayBackground = mResources.getDrawable(R.drawable.month_view_today_background);
247
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);
258
259         if (mShowToast) {
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));
270             ta.recycle();
271         }
272
273         mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
274             @Override
275             public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
276                     float velocityY) {
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;
282
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) {
286                     return false;
287                 }
288
289                 // Switch to a different month
290                 Time time = mOtherViewCalendar;
291                 time.set(mViewCalendar);
292                 if (velocityY < 0) {
293                     time.month += 1;
294                 } else {
295                     time.month -= 1;
296                 }
297                 time.normalize(true);
298                 mParentActivity.goTo(time, true);
299
300                 return true;
301             }
302
303             @Override
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;
308                 return true;
309             }
310
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);
316                 if (row > 5) {
317                     row = 5;
318                 }
319                 if (col > 6) {
320                     col = 6;
321                 }
322
323                 // Highlight the selected day.
324                 mCursor.setSelectedRowColumn(row, col);
325             }
326
327             @Override
328             public void onShowPress(MotionEvent e) {
329                 // Highlight the selected day.
330                 setSelectedCell(e);
331                 mSelectionMode = SELECTION_PRESSED;
332                 mRedrawScreen = true;
333                 invalidate();
334             }
335
336             @Override
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;
345                     invalidate();
346                     performLongClick();
347                 }
348             }
349
350             @Override
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;
357
358                 if (mSelectionMode != SELECTION_HIDDEN) {
359                     mSelectionMode = SELECTION_HIDDEN;
360                     mRedrawScreen = true;
361                     invalidate();
362                 }
363                 return true;
364             }
365
366             @Override
367             public boolean onSingleTapUp(MotionEvent e) {
368                 if (mLaunchDayView) {
369                     setSelectedCell(e);
370                     mSelectionMode = SELECTION_SELECTED;
371                     mRedrawScreen = true;
372                     invalidate();
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);
378                 }
379
380                 return true;
381             }
382         });
383     }
384
385     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
386         MenuItem item;
387
388         final long startMillis = getSelectedTimeInMillis();
389         final int flags = DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_DATE
390                 | DateUtils.FORMAT_ABBREV_MONTH;
391
392         final String title = DateUtils.formatDateTime(mParentActivity, startMillis, flags);
393         menu.setHeaderTitle(title);
394
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');
399
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');
404
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');
409     }
410
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);
417                     break;
418                 }
419                 case MenuHelper.MENU_AGENDA: {
420                     long startMillis = getSelectedTimeInMillis();
421                     Utils.startActivity(mParentActivity, AgendaActivity.class.getName(), startMillis);
422                     break;
423                 }
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);
432                     break;
433                 }
434                 default: {
435                     return false;
436                 }
437             }
438             return true;
439         }
440     }
441
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;
447         monthStart.hour = 0;
448         monthStart.minute = 0;
449         monthStart.second = 0;
450         long millis = monthStart.normalize(true /* ignore isDst */);
451         int startDay = Time.getJulianDay(millis, monthStart.gmtoff);
452
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();
458         } else {
459             // To avoid a compiler error that this variable might not be initialized.
460             startMillis = 0;
461         }
462
463         final ArrayList<Event> events = new ArrayList<Event>();
464         mEventLoader.loadEventsInBackground(EVENT_NUM_DAYS, events, millis, new Runnable() {
465             public void run() {
466                 mEvents = events;
467                 mRedrawScreen = true;
468                 mParentActivity.stopProgressSpinner();
469                 invalidate();
470                 int numEvents = events.size();
471
472                 // Clear out event days
473                 for (int i = 0; i < EVENT_NUM_DAYS; i++) {
474                     eventDay[i] = false;
475                 }
476
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) {
483                         if (startDay < 0) {
484                             startDay = 0;
485                         }
486                         if (startDay > 31) {
487                             startDay = 31;
488                         }
489                         if (endDay < 0) {
490                             endDay = 0;
491                         }
492                         if (endDay > 31) {
493                             endDay = 31;
494                         }
495                         for (int j = startDay; j < endDay; j++) {
496                             eventDay[j] = true;
497                         }
498                     }
499                 }
500             }
501         }, null);
502     }
503
504     void animationStarted() {
505         mAnimating = true;
506     }
507
508     void animationFinished() {
509         mAnimating = false;
510         mRedrawScreen = true;
511         invalidate();
512     }
513
514     @Override
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...
518         clearBitmapCache();
519     }
520
521     @Override
522     protected void onDetachedFromWindow() {
523         super.onDetachedFromWindow();
524         // No need to hang onto the bitmaps...
525         clearBitmapCache();
526         if (mBitmap != null) {
527             mBitmap.recycle();
528         }
529     }
530
531     @Override
532     protected void onDraw(Canvas canvas) {
533         if (mRedrawScreen) {
534             if (mCanvas == null) {
535                 drawingCalc(getWidth(), getHeight());
536             }
537
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;
545             }
546         }
547
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);
551         }
552     }
553
554     private void doDraw(Canvas canvas) {
555         boolean isLandscape = getResources().getConfiguration().orientation
556                 == Configuration.ORIENTATION_LANDSCAPE;
557
558         Paint p = new Paint();
559         Rect r = mRect;
560         int columnDay1 = mCursor.getColumnOf(1);
561
562         // Get the Julian day for the date at row 0, column 0.
563         int day = mFirstJulianDay - columnDay1;
564
565         int weekNum = 0;
566         Calendar calendar = null;
567         if (mShowWeekNumbers) {
568             calendar = Calendar.getInstance();
569             boolean noPrevMonth = (columnDay1 == 0);
570
571             // Compute the week number for the first row.
572             weekNum = getWeekOfYear(0, 0, noPrevMonth, calendar);
573         }
574
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);
578                 day += 1;
579             }
580
581             if (mShowWeekNumbers) {
582                 weekNum += 1;
583                 if (weekNum >= 53) {
584                     boolean inCurrentMonth = (day - mFirstJulianDay < 31);
585                     weekNum = getWeekOfYear(row + 1, 0, inCurrentMonth, calendar);
586                 }
587             }
588         }
589
590         drawGrid(canvas, p);
591     }
592
593     @Override
594     public boolean onTouchEvent(MotionEvent event) {
595         if (mGestureDetector.onTouchEvent(event)) {
596             return true;
597         }
598
599         return super.onTouchEvent(event);
600     }
601
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);
605         if (column > 6) {
606             column = 6;
607         }
608
609         DayOfMonthCursor c = mCursor;
610         Time time = mTempTime;
611         time.set(mViewCalendar);
612
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);
620     }
621
622     /**
623      * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect.
624      *
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
629      */
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());
633
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);
638         return bitmap;
639     }
640
641     /**
642      * Clears the bitmap cache. Generally only needed when the screen size changed.
643      */
644     private void clearBitmapCache() {
645         recycleAndClearBitmapCache(mDayBitmapCache);
646     }
647
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();
652         }
653         bitmapCache.clear();
654
655     }
656
657     /**
658      * Draw the grid lines for the calendar
659      * @param canvas The canvas to draw on.
660      * @param p The paint used for drawing.
661      */
662     private void drawGrid(Canvas canvas, Paint p) {
663         p.setColor(mMonthOtherMonthColor);
664         p.setAntiAlias(false);
665
666         final int width = getMeasuredWidth();
667         final int height = getMeasuredHeight();
668
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);
672         }
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);
676         }
677     }
678
679     /**
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.
689      */
690     private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p,
691             Rect r, boolean isLandscape) {
692
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);
698         }
699
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) {
705             isToday = true;
706         }
707
708         int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight);
709         int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth);
710
711         r.left = x;
712         r.top = y;
713         r.right = x + mCellWidth;
714         r.bottom = y + mCellHeight;
715
716
717         // Adjust the left column, right column, and bottom row to leave
718         // no border.
719         if (column == 0) {
720             r.left = -1;
721         } else if (column == 6) {
722             r.right += mBorder + 2;
723         }
724
725         if (row == 5) {
726             r.bottom = getMeasuredHeight();
727         }
728
729
730         // Draw the cell contents (excluding monthDay number)
731         if (!withinCurrentMonth) {
732             boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column);
733
734             // Adjust cell boundaries to compensate for the different border
735             // style.
736             r.top--;
737             if (column != 0) {
738                 r.left--;
739             }
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);
750             } else {
751                 mBoxLongPressed.setBounds(r);
752                 mBoxLongPressed.draw(canvas);
753             }
754
755             //Places events for that day
756             drawEvents(day, canvas, r, p, false /*draw bb background*/);
757             if (!mAnimating) {
758                 updateEventDetails(day);
759             }
760         } else {
761             // Today gets a different background
762             if (isToday) {
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);
768             }
769             //Places events for that day
770             drawEvents(day, canvas, r, p, !isToday /*draw bb background*/);
771         }
772
773         // Draw week number
774         if (mShowWeekNumbers && column == 0) {
775             // Draw the banner
776             p.setStyle(Paint.Style.FILL);
777             p.setColor(mMonthWeekBannerColor);
778             if (isLandscape) {
779                 int bottom = r.bottom;
780                 r.bottom = r.top + WEEK_BANNER_HEIGHT;
781                 r.left++;
782                 canvas.drawRect(r, p);
783                 r.bottom = bottom;
784                 r.left--;
785             } else {
786                 int top = r.top;
787                 r.top = r.bottom - WEEK_BANNER_HEIGHT;
788                 r.left++;
789                 canvas.drawRect(r, p);
790                 r.top = top;
791                 r.left--;
792             }
793
794             // Draw the number
795             p.setColor(mMonthOtherMonthBannerColor);
796             p.setAntiAlias(true);
797             p.setTypeface(null);
798             p.setTextSize(WEEK_TEXT_SIZE);
799             p.setTextAlign(Paint.Align.LEFT);
800
801             int textX = r.left + WEEK_TEXT_PADDING;
802             int textY;
803             if (isLandscape) {
804                 textY = r.top + WEEK_BANNER_HEIGHT - WEEK_TEXT_PADDING;
805             } else {
806                 textY = r.bottom - WEEK_TEXT_PADDING;
807             }
808
809             canvas.drawText(String.valueOf(weekNum), textX, textY, p);
810         }
811
812         // Draw the monthDay number
813         p.setStyle(Paint.Style.FILL);
814         p.setAntiAlias(true);
815         p.setTypeface(null);
816         p.setTextSize(MONTH_DAY_TEXT_SIZE);
817
818         if (!withinCurrentMonth) {
819             p.setColor(mMonthOtherMonthDayNumberColor);
820         } else {
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);
827             } else {
828                 p.setColor(mMonthDayNumberColor);
829             }
830             //bolds the day if there's an event that day
831             p.setFakeBoldText(eventDay[day-mFirstJulianDay]);
832         }
833         /*Drawing of day number is done here
834          *easy to find tags draw number draw day*/
835         p.setTextAlign(Paint.Align.CENTER);
836         // center of text
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);
840     }
841
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;
847
848         Style oldStyle = p.getStyle();
849         int oldColor = p.getColor();
850
851         ArrayList<Event> events = mEvents;
852         int numEvents = events.size();
853         EventGeometry geometry = mEventGeometry;
854
855         if (drawBg) {
856             RectF rf = mRectF;
857             rf.left = left;
858             rf.right = left + BUSY_BITS_WIDTH;
859             rf.bottom = rect.bottom - BUSY_BITS_MARGIN;
860             rf.top = top;
861
862             p.setColor(LIGHT_GRAY_BGCOLOR);
863             p.setStyle(Style.FILL);
864             canvas.drawRect(rf, p);
865         }
866
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)) {
870                 continue;
871             }
872             drawEventRect(rect, event, canvas, p);
873         }
874
875     }
876
877     // Draw busybits for a single event
878     private RectF drawEventRect(Rect rect, Event event, Canvas canvas, Paint p) {
879
880         p.setColor(BUSY_BITS_COLOR);
881
882         int left = rect.right - BUSY_BITS_MARGIN - BUSY_BITS_WIDTH;
883         int bottom = rect.bottom - BUSY_BITS_MARGIN;
884
885         RectF rf = mRectF;
886         rf.top = event.top;
887         // Make sure we don't go below the bottom of the bb bar
888         rf.bottom = Math.min(event.bottom, bottom);
889         rf.left = left;
890         rf.right = left + BUSY_BITS_WIDTH;
891
892         canvas.drawRect(rf, p);
893
894         return rf;
895     }
896
897     private boolean isFirstDayOfNextMonth(int row, int column) {
898         if (column == 0) {
899             column = 6;
900             row--;
901         } else {
902             column--;
903         }
904         return mCursor.isWithinCurrentMonth(row, column);
905     }
906
907     private int getWeekOfYear(int row, int column, boolean isWithinCurrentMonth,
908             Calendar calendar) {
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());
913         } else {
914             int month = mCursor.getMonth();
915             int year = mCursor.getYear();
916             if (row < 2) {
917                 // Previous month
918                 if (month == 0) {
919                     year--;
920                     month = 11;
921                 } else {
922                     month--;
923                 }
924             } else {
925                 // Next month
926                 if (month == 11) {
927                     year++;
928                     month = 0;
929                 } else {
930                     month++;
931                 }
932             }
933             calendar.set(Calendar.MONTH, month);
934             calendar.set(Calendar.YEAR, year);
935         }
936
937         return calendar.get(Calendar.WEEK_OF_YEAR);
938     }
939
940     void setDetailedView(String detailedView) {
941         mDetailedView = detailedView;
942     }
943
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);
947
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);
953
954         mCursor = new DayOfMonthCursor(time.year, time.month, time.monthDay,
955                 mCursor.getWeekStartDay());
956
957         mRedrawScreen = true;
958         invalidate();
959     }
960
961     public long getSelectedTimeInMillis() {
962         Time time = mTempTime;
963         time.set(mViewCalendar);
964
965         time.month += mCursor.getSelectedMonthOffset();
966         time.monthDay = mCursor.getSelectedDayOfMonth();
967
968         // Restore the saved hour:minute:second offset from when we entered
969         // this view.
970         time.second = mSavedTime.second;
971         time.minute = mSavedTime.minute;
972         time.hour = mSavedTime.hour;
973         return time.normalize(true);
974     }
975
976     Time getTime() {
977         return mViewCalendar;
978     }
979
980     public int getSelectionMode() {
981         return mSelectionMode;
982     }
983
984     public void setSelectionMode(int selectionMode) {
985         mSelectionMode = selectionMode;
986     }
987
988     private void drawingCalc(int width, int height) {
989         mCellHeight = (height - (6 * WEEK_GAP)) / 6;
990         mEventGeometry
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;
994
995         if (mShowToast) {
996             mPopup.dismiss();
997             mPopup.setWidth(width - 20);
998             mPopup.setHeight(POPUP_HEIGHT);
999         }
1000
1001         if (((mBitmap == null)
1002                     || mBitmap.isRecycled()
1003                     || (mBitmap.getHeight() != height)
1004                     || (mBitmap.getWidth() != width))
1005                 && (width > 0) && (height > 0)) {
1006             if (mBitmap != null) {
1007                 mBitmap.recycle();
1008             }
1009             mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
1010             mCanvas = new Canvas(mBitmap);
1011         }
1012
1013         mBitmapRect.top = 0;
1014         mBitmapRect.bottom = height;
1015         mBitmapRect.left = 0;
1016         mBitmapRect.right = width;
1017     }
1018
1019     private void updateEventDetails(int date) {
1020         if (!mShowToast) {
1021             return;
1022         }
1023
1024         getHandler().removeCallbacks(mDismissPopup);
1025         ArrayList<Event> events = mEvents;
1026         int numEvents = events.size();
1027         if (numEvents == 0) {
1028             mPopup.dismiss();
1029             return;
1030         }
1031
1032         int eventIndex = 0;
1033         for (int i = 0; i < numEvents; i++) {
1034             Event event = events.get(i);
1035
1036             if (event.startDay > date || event.endDay < date) {
1037                 continue;
1038             }
1039
1040             // If we have all the event that we can display, then just count
1041             // the extra ones.
1042             if (eventIndex >= 4) {
1043                 eventIndex += 1;
1044                 continue;
1045             }
1046
1047             int flags;
1048             boolean showEndTime = false;
1049             if (event.allDay) {
1050                 int numDays = event.endDay - event.startDay;
1051                 if (numDays == 0) {
1052                     flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
1053                             | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
1054                 } else {
1055                     showEndTime = true;
1056                     flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
1057                             | DateUtils.FORMAT_ABBREV_ALL;
1058                 }
1059             } else {
1060                 flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
1061                 if (DateFormat.is24HourFormat(mParentActivity)) {
1062                     flags |= DateUtils.FORMAT_24HOUR;
1063                 }
1064             }
1065
1066             String timeRange;
1067             if (showEndTime) {
1068                 timeRange = DateUtils.formatDateRange(mParentActivity,
1069                         event.startMillis, event.endMillis, flags);
1070             } else {
1071                 timeRange = DateUtils.formatDateRange(mParentActivity,
1072                         event.startMillis, event.startMillis, flags);
1073             }
1074
1075             TextView timeView = null;
1076             TextView titleView = null;
1077             switch (eventIndex) {
1078                 case 0:
1079                     timeView = (TextView) mPopupView.findViewById(R.id.time0);
1080                     titleView = (TextView) mPopupView.findViewById(R.id.event_title0);
1081                     break;
1082                 case 1:
1083                     timeView = (TextView) mPopupView.findViewById(R.id.time1);
1084                     titleView = (TextView) mPopupView.findViewById(R.id.event_title1);
1085                     break;
1086                 case 2:
1087                     timeView = (TextView) mPopupView.findViewById(R.id.time2);
1088                     titleView = (TextView) mPopupView.findViewById(R.id.event_title2);
1089                     break;
1090                 case 3:
1091                     timeView = (TextView) mPopupView.findViewById(R.id.time3);
1092                     titleView = (TextView) mPopupView.findViewById(R.id.event_title3);
1093                     break;
1094             }
1095
1096             timeView.setText(timeRange);
1097             titleView.setText(event.title);
1098             eventIndex += 1;
1099         }
1100         if (eventIndex == 0) {
1101             // We didn't find any events for this day
1102             mPopup.dismiss();
1103             return;
1104         }
1105
1106         // Hide the items that have no event information
1107         View view;
1108         switch (eventIndex) {
1109             case 1:
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);
1118                 break;
1119             case 2:
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);
1128                 break;
1129             case 3:
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);
1138                 break;
1139             case 4:
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);
1148                 break;
1149             default:
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);
1161                 break;
1162         }
1163
1164         if (eventIndex > 5) {
1165             eventIndex = 5;
1166         }
1167         int popupHeight = 20 * eventIndex + 15;
1168         mPopup.setHeight(popupHeight);
1169
1170         if (mPreviousPopupHeight != popupHeight) {
1171             mPreviousPopupHeight = popupHeight;
1172             mPopup.dismiss();
1173         }
1174         mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
1175         postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
1176     }
1177
1178     @Override
1179     public boolean onKeyUp(int keyCode, KeyEvent event) {
1180         long duration = event.getEventTime() - event.getDownTime();
1181
1182         switch (keyCode) {
1183         case KeyEvent.KEYCODE_DPAD_CENTER:
1184             if (mSelectionMode == SELECTION_HIDDEN) {
1185                 // Don't do anything unless the selection is visible.
1186                 break;
1187             }
1188
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;
1196                 invalidate();
1197                 break;
1198             }
1199
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);
1204             } else {
1205                 mSelectionMode = SELECTION_LONGPRESS;
1206                 mRedrawScreen = true;
1207                 invalidate();
1208                 performLongClick();
1209             }
1210         }
1211         return super.onKeyUp(keyCode, event);
1212     }
1213
1214     @Override
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;
1224                 invalidate();
1225                 return 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;
1231                 invalidate();
1232                 return true;
1233             }
1234         }
1235
1236         mSelectionMode = SELECTION_SELECTED;
1237         boolean redraw = false;
1238         Time other = null;
1239
1240         switch (keyCode) {
1241         case KeyEvent.KEYCODE_ENTER:
1242             long millis = getSelectedTimeInMillis();
1243             Utils.startActivity(getContext(), mDetailedView, millis);
1244             return true;
1245         case KeyEvent.KEYCODE_DPAD_UP:
1246             if (mCursor.up()) {
1247                 other = mOtherViewCalendar;
1248                 other.set(mViewCalendar);
1249                 other.month -= 1;
1250                 other.monthDay = mCursor.getSelectedDayOfMonth();
1251
1252                 // restore the calendar cursor for the animation
1253                 mCursor.down();
1254             }
1255             redraw = true;
1256             break;
1257
1258         case KeyEvent.KEYCODE_DPAD_DOWN:
1259             if (mCursor.down()) {
1260                 other = mOtherViewCalendar;
1261                 other.set(mViewCalendar);
1262                 other.month += 1;
1263                 other.monthDay = mCursor.getSelectedDayOfMonth();
1264
1265                 // restore the calendar cursor for the animation
1266                 mCursor.up();
1267             }
1268             redraw = true;
1269             break;
1270
1271         case KeyEvent.KEYCODE_DPAD_LEFT:
1272             if (mCursor.left()) {
1273                 other = mOtherViewCalendar;
1274                 other.set(mViewCalendar);
1275                 other.month -= 1;
1276                 other.monthDay = mCursor.getSelectedDayOfMonth();
1277
1278                 // restore the calendar cursor for the animation
1279                 mCursor.right();
1280             }
1281             redraw = true;
1282             break;
1283
1284         case KeyEvent.KEYCODE_DPAD_RIGHT:
1285             if (mCursor.right()) {
1286                 other = mOtherViewCalendar;
1287                 other.set(mViewCalendar);
1288                 other.month += 1;
1289                 other.monthDay = mCursor.getSelectedDayOfMonth();
1290
1291                 // restore the calendar cursor for the animation
1292                 mCursor.left();
1293             }
1294             redraw = true;
1295             break;
1296         }
1297
1298         if (other != null) {
1299             other.normalize(true /* ignore DST */);
1300             mNavigator.goTo(other, true);
1301         } else if (redraw) {
1302             mRedrawScreen = true;
1303             invalidate();
1304         }
1305
1306         return redraw;
1307     }
1308
1309     class DismissPopup implements Runnable {
1310         public void run() {
1311             mPopup.dismiss();
1312         }
1313     }
1314
1315     // This is called when the activity is paused so that the popup can
1316     // be dismissed.
1317     void dismissPopup() {
1318         if (!mShowToast) {
1319             return;
1320         }
1321
1322         // Protect against null-pointer exceptions
1323         if (mPopup != null) {
1324             mPopup.dismiss();
1325         }
1326
1327         Handler handler = getHandler();
1328         if (handler != null) {
1329             handler.removeCallbacks(mDismissPopup);
1330         }
1331     }
1332 }