summaryrefslogtreecommitdiffstats
path: root/src/com/android/calendar/MonthView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/calendar/MonthView.java')
-rw-r--r--src/com/android/calendar/MonthView.java1351
1 files changed, 1351 insertions, 0 deletions
diff --git a/src/com/android/calendar/MonthView.java b/src/com/android/calendar/MonthView.java
new file mode 100644
index 00000000..5949d956
--- /dev/null
+++ b/src/com/android/calendar/MonthView.java
@@ -0,0 +1,1351 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calendar;
+
+import static android.provider.Calendar.EVENT_BEGIN_TIME;
+import static android.provider.Calendar.EVENT_END_TIME;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.pim.DateFormat;
+import android.pim.DateUtils;
+import android.pim.Time;
+import android.provider.Calendar.BusyBits;
+import android.util.DayOfMonthCursor;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+
+public class MonthView extends View implements View.OnCreateContextMenuListener {
+
+ private static final boolean PROFILE_LOAD_TIME = false;
+ private static final boolean DEBUG_BUSYBITS = false;
+
+ private static final int WEEK_GAP = 1;
+ private static final int MONTH_DAY_GAP = 1;
+ private static final float HOUR_GAP = 0.5f;
+
+ private static final int MONTH_DAY_TEXT_SIZE = 20;
+ private static final int WEEK_BANNER_HEIGHT = 17;
+ private static final int WEEK_TEXT_SIZE = 15;
+ private static final int WEEK_TEXT_PADDING = 3;
+ private static final int BUSYBIT_WIDTH = 10;
+ private static final int BUSYBIT_RIGHT_MARGIN = 3;
+ private static final int BUSYBIT_TOP_BOTTOM_MARGIN = 7;
+
+ private static final int HORIZONTAL_FLING_THRESHOLD = 50;
+
+ private int mCellHeight;
+ private int mBorder;
+ private boolean mLaunchDayView;
+
+ private GestureDetector mGestureDetector;
+
+ private String mDetailedView = CalendarPreferenceActivity.DEFAULT_DETAILED_VIEW;
+
+ private Time mToday;
+ private Time mViewCalendar;
+ private Time mSavedTime = new Time(); // the time when we entered this view
+
+ // This Time object is used to set the time for the other Month view.
+ private Time mOtherViewCalendar = new Time();
+
+ // This Time object is used for temporary calculations and is allocated
+ // once to avoid extra garbage collection
+ private Time mTempTime = new Time();
+
+ private DayOfMonthCursor mCursor;
+
+ private Drawable mBoxSelected;
+ private Drawable mBoxPressed;
+ private Drawable mBoxLongPressed;
+ private Drawable mDnaEmpty;
+ private Drawable mDnaTop;
+ private Drawable mDnaMiddle;
+ private Drawable mDnaBottom;
+ private int mCellWidth;
+
+ private Resources mResources;
+ private MonthActivity mParentActivity;
+ private Navigator mNavigator;
+ private final EventGeometry mEventGeometry;
+
+ // Pre-allocate and reuse
+ private Rect mRect = new Rect();
+
+ // The number of hours represented by one busy bit
+ private static final int HOURS_PER_BUSY_SLOT = 4;
+
+ // The number of database intervals represented by one busy bit (slot)
+ private static final int INTERVALS_PER_BUSY_SLOT = 4 * 60 / BusyBits.MINUTES_PER_BUSY_INTERVAL;
+
+ // The bit mask for coalescing the raw busy bits from the database
+ // (1 bit per hour) into the busy bits per slot (4-hour slots).
+ private static final int BUSY_SLOT_MASK = (1 << INTERVALS_PER_BUSY_SLOT) - 1;
+
+ // The number of slots in a day
+ private static final int SLOTS_PER_DAY = 24 / HOURS_PER_BUSY_SLOT;
+
+ // There is one "busy" bit for each slot of time.
+ private byte[][] mBusyBits = new byte[31][SLOTS_PER_DAY];
+
+ // Raw busy bits from database
+ private int[] mRawBusyBits = new int[31];
+ private int[] mAllDayCounts = new int[31];
+
+ private PopupWindow mPopup;
+ private View mPopupView;
+ private static final int POPUP_HEIGHT = 100;
+ private int mPreviousPopupHeight;
+ private static final int POPUP_DISMISS_DELAY = 3000;
+ private DismissPopup mDismissPopup = new DismissPopup();
+
+ // For drawing to an off-screen Canvas
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+ private boolean mRedrawScreen = true;
+ private Rect mBitmapRect = new Rect();
+ private boolean mAnimating;
+
+ // These booleans disable features that were taken out of the spec.
+ private boolean mShowWeekNumbers = false;
+ private boolean mShowToast = false;
+
+ // Bitmap caches.
+ // These improve performance by minimizing calls to NinePatchDrawable.draw() for common
+ // drawables for events and day backgrounds.
+ // mEventBitmapCache is indexed by an integer constructed from the bits in the busyBits
+ // field. It is not expected to be larger than 12 bits (if so, we should switch to using a Map).
+ // mDayBitmapCache is indexed by a unique integer constructed from the width/height.
+ private SparseArray<Bitmap> mEventBitmapCache = new SparseArray<Bitmap>(1<<SLOTS_PER_DAY);
+ private SparseArray<Bitmap> mDayBitmapCache = new SparseArray<Bitmap>(4);
+
+ private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
+
+ /**
+ * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
+ */
+ private static final int SELECTION_HIDDEN = 0;
+ private static final int SELECTION_PRESSED = 1;
+ private static final int SELECTION_SELECTED = 2;
+ private static final int SELECTION_LONGPRESS = 3;
+
+ // Modulo used to pack (width,height) into a unique integer
+ private static final int MODULO_SHIFT = 16;
+
+ private int mSelectionMode = SELECTION_HIDDEN;
+
+ /**
+ * The first Julian day of the current month.
+ */
+ private int mFirstJulianDay;
+
+ private final EventLoader mEventLoader;
+
+ private ArrayList<Event> mEvents = new ArrayList<Event>();
+
+ private Drawable mTodayBackground;
+ private Drawable mDayBackground;
+
+ // Cached colors
+ private int mMonthOtherMonthColor;
+ private int mMonthWeekBannerColor;
+ private int mMonthOtherMonthBannerColor;
+ private int mMonthOtherMonthDayNumberColor;
+ private int mMonthDayNumberColor;
+ private int mMonthTodayNumberColor;
+
+ public MonthView(MonthActivity activity, Navigator navigator) {
+ super(activity);
+ mEventLoader = activity.mEventLoader;
+ mNavigator = navigator;
+ mEventGeometry = new EventGeometry();
+ mEventGeometry.setMinEventHeight(1.0f);
+ mEventGeometry.setHourGap(HOUR_GAP);
+ init(activity);
+ }
+
+ private void init(MonthActivity activity) {
+ setFocusable(true);
+ setClickable(true);
+ setOnCreateContextMenuListener(this);
+ mParentActivity = activity;
+ mViewCalendar = new Time();
+ long now = System.currentTimeMillis();
+ mViewCalendar.set(now);
+ mViewCalendar.monthDay = 1;
+ long millis = mViewCalendar.normalize(true /* ignore DST */);
+ mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
+ mViewCalendar.set(now);
+
+ mCursor = new DayOfMonthCursor(mViewCalendar.year, mViewCalendar.month,
+ mViewCalendar.monthDay, mParentActivity.getStartDay());
+ mToday = new Time();
+ mToday.set(System.currentTimeMillis());
+
+ mResources = activity.getResources();
+ mBoxSelected = mResources.getDrawable(R.drawable.month_view_selected);
+ mBoxPressed = mResources.getDrawable(R.drawable.month_view_pressed);
+ mBoxLongPressed = mResources.getDrawable(R.drawable.month_view_longpress);
+
+ mDnaEmpty = mResources.getDrawable(R.drawable.dna_empty);
+ mDnaTop = mResources.getDrawable(R.drawable.dna_1_of_6);
+ mDnaMiddle = mResources.getDrawable(R.drawable.dna_2345_of_6);
+ mDnaBottom = mResources.getDrawable(R.drawable.dna_6_of_6);
+ mTodayBackground = mResources.getDrawable(R.drawable.month_view_today_background);
+ mDayBackground = mResources.getDrawable(R.drawable.month_view_background);
+
+ // Cache color lookups
+ Resources res = getResources();
+ mMonthOtherMonthColor = res.getColor(R.color.month_other_month);
+ mMonthWeekBannerColor = res.getColor(R.color.month_week_banner);
+ mMonthOtherMonthBannerColor = res.getColor(R.color.month_other_month_banner);
+ mMonthOtherMonthDayNumberColor = res.getColor(R.color.month_other_month_day_number);
+ mMonthDayNumberColor = res.getColor(R.color.month_day_number);
+ mMonthTodayNumberColor = res.getColor(R.color.month_today_number);
+
+ if (mShowToast) {
+ LayoutInflater inflater;
+ inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mPopupView = inflater.inflate(R.layout.month_bubble, null);
+ mPopup = new PopupWindow(activity);
+ mPopup.setContentView(mPopupView);
+ Resources.Theme dialogTheme = getResources().newTheme();
+ dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
+ TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
+ android.R.attr.windowBackground });
+ mPopup.setBackgroundDrawable(ta.getDrawable(0));
+ ta.recycle();
+ }
+
+ mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ // The user might do a slow "fling" after touching the screen
+ // and we don't want the long-press to pop up a context menu.
+ // Setting mLaunchDayView to false prevents the long-press.
+ mLaunchDayView = false;
+ mSelectionMode = SELECTION_HIDDEN;
+
+ int distanceX = Math.abs((int) e2.getX() - (int) e1.getX());
+ int distanceY = Math.abs((int) e2.getY() - (int) e1.getY());
+ if (distanceY < HORIZONTAL_FLING_THRESHOLD || distanceY < distanceX) {
+ return false;
+ }
+
+ // Switch to a different month
+ Time time = mOtherViewCalendar;
+ time.set(mViewCalendar);
+ if (velocityY < 0) {
+ time.month += 1;
+ } else {
+ time.month -= 1;
+ }
+ time.normalize(true);
+ mParentActivity.goTo(time);
+
+ return true;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ mLaunchDayView = false;
+ return true;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ int x = (int) e.getX();
+ int y = (int) e.getY();
+ int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
+ int col = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
+ if (row > 5) {
+ row = 5;
+ }
+ if (col > 6) {
+ col = 6;
+ }
+
+ // Launch the Day/Agenda view when the finger lifts up,
+ // unless the finger moves before lifting up.
+ mLaunchDayView = true;
+
+ // Highlight the selected day.
+ mCursor.setSelectedRowColumn(row, col);
+ mSelectionMode = SELECTION_PRESSED;
+ mRedrawScreen = true;
+ invalidate();
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ // If mLaunchDayView is true, then we haven't done any scrolling
+ // after touching the screen, so allow long-press to proceed
+ // with popping up the context menu.
+ if (mLaunchDayView) {
+ mLaunchDayView = false;
+ mSelectionMode = SELECTION_LONGPRESS;
+ mRedrawScreen = true;
+ invalidate();
+ performLongClick();
+ }
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2,
+ float distanceX, float distanceY) {
+ // If the user moves his finger after touching, then do not
+ // launch the Day view when he lifts his finger. Also, turn
+ // off the selection.
+ mLaunchDayView = false;
+
+ if (mSelectionMode != SELECTION_HIDDEN) {
+ mSelectionMode = SELECTION_HIDDEN;
+ mRedrawScreen = true;
+ invalidate();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ if (mLaunchDayView) {
+ mSelectionMode = SELECTION_SELECTED;
+ mRedrawScreen = true;
+ invalidate();
+ mLaunchDayView = false;
+ int x = (int) e.getX();
+ int y = (int) e.getY();
+ long millis = getSelectedMillisFor(x, y);
+ Utils.startActivity(getContext(), mDetailedView, millis);
+ mParentActivity.finish();
+ }
+
+ return true;
+ }
+ });
+ }
+
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
+ MenuItem item;
+
+ item = menu.add(0, MenuHelper.MENU_DAY, 0, R.string.day_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_day);
+ item.setAlphabeticShortcut('d');
+
+ item = menu.add(0, MenuHelper.MENU_AGENDA, 0, R.string.agenda_view);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_agenda);
+ item.setAlphabeticShortcut('a');
+
+ item = menu.add(0, MenuHelper.MENU_EVENT_CREATE, 0, R.string.event_create);
+ item.setOnMenuItemClickListener(mContextMenuHandler);
+ item.setIcon(android.R.drawable.ic_menu_add);
+ item.setAlphabeticShortcut('n');
+ }
+
+ private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case MenuHelper.MENU_DAY: {
+ long startMillis = getSelectedTimeInMillis();
+ MenuHelper.switchTo(mParentActivity, DayActivity.class.getName(), startMillis);
+ mParentActivity.finish();
+ break;
+ }
+ case MenuHelper.MENU_AGENDA: {
+ long startMillis = getSelectedTimeInMillis();
+ MenuHelper.switchTo(mParentActivity, AgendaActivity.class.getName(), startMillis);
+ mParentActivity.finish();
+ break;
+ }
+ case MenuHelper.MENU_EVENT_CREATE: {
+ long startMillis = getSelectedTimeInMillis();
+ long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setClassName(mContext, EditEvent.class.getName());
+ intent.putExtra(EVENT_BEGIN_TIME, startMillis);
+ intent.putExtra(EVENT_END_TIME, endMillis);
+ mParentActivity.startActivity(intent);
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ void reloadEvents() {
+ // Get the date for the beginning of the month
+ Time monthStart = mTempTime;
+ monthStart.set(mViewCalendar);
+ monthStart.monthDay = 1;
+ monthStart.hour = 0;
+ monthStart.minute = 0;
+ monthStart.second = 0;
+ long millis = monthStart.normalize(true /* ignore isDst */);
+ int startDay = Time.getJulianDay(millis, monthStart.gmtoff);
+
+ // Load the busy-bits in the background
+ mParentActivity.startProgressSpinner();
+ final long startMillis;
+ if (PROFILE_LOAD_TIME) {
+ startMillis = SystemClock.uptimeMillis();
+ } else {
+ // To avoid a compiler error that this variable might not be initialized.
+ startMillis = 0;
+ }
+ mEventLoader.loadBusyBitsInBackground(startDay, 31, mRawBusyBits, mAllDayCounts,
+ new Runnable() {
+ public void run() {
+ convertBusyBits();
+ if (PROFILE_LOAD_TIME) {
+ long endMillis = SystemClock.uptimeMillis();
+ long elapsed = endMillis - startMillis;
+ Log.i("Cal", (mViewCalendar.month+1) + "/" + mViewCalendar.year + " Month view load busybits: " + elapsed);
+ }
+ mRedrawScreen = true;
+ mParentActivity.stopProgressSpinner();
+ invalidate();
+ }
+ });
+ }
+
+ void animationStarted() {
+ mAnimating = true;
+ }
+
+ void animationFinished() {
+ mAnimating = false;
+ mRedrawScreen = true;
+ invalidate();
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldw, int oldh) {
+ drawingCalc(width, height);
+ // If the size changed, then we should rebuild the bitmaps...
+ clearBitmapCache();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ // No need to hang onto the bitmaps...
+ clearBitmapCache();
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mRedrawScreen) {
+ if (mCanvas == null) {
+ drawingCalc(getWidth(), getHeight());
+ }
+
+ // If we are zero-sized, the canvas will remain null so check again
+ if (mCanvas != null) {
+ // Clear the background
+ final Canvas bitmapCanvas = mCanvas;
+ bitmapCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ doDraw(bitmapCanvas);
+ mRedrawScreen = false;
+ }
+ }
+
+ // If we are zero-sized, the bitmap will be null so guard against this
+ if (mBitmap != null) {
+ canvas.drawBitmap(mBitmap, mBitmapRect, mBitmapRect, null);
+ }
+ }
+
+ private void doDraw(Canvas canvas) {
+ boolean isLandscape = getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+
+ Paint p = new Paint();
+ Rect r = mRect;
+ int columnDay1 = mCursor.getColumnOf(1);
+
+ // Get the Julian day for the date at row 0, column 0.
+ int day = mFirstJulianDay - columnDay1;
+
+ int weekNum = 0;
+ Calendar calendar = null;
+ if (mShowWeekNumbers) {
+ calendar = Calendar.getInstance();
+ boolean noPrevMonth = (columnDay1 == 0);
+
+ // Compute the week number for the first row.
+ weekNum = getWeekOfYear(0, 0, noPrevMonth, calendar);
+ }
+
+ for (int row = 0; row < 6; row++) {
+ for (int column = 0; column < 7; column++) {
+ drawBox(day, weekNum, row, column, canvas, p, r, isLandscape);
+ day += 1;
+ }
+
+ if (mShowWeekNumbers) {
+ weekNum += 1;
+ if (weekNum >= 53) {
+ boolean inCurrentMonth = (day - mFirstJulianDay < 31);
+ weekNum = getWeekOfYear(row + 1, 0, inCurrentMonth, calendar);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mGestureDetector.onTouchEvent(event)) {
+ return true;
+ }
+
+ return super.onTouchEvent(event);
+ }
+
+ private long getSelectedMillisFor(int x, int y) {
+ int row = (y - WEEK_GAP) / (WEEK_GAP + mCellHeight);
+ int column = (x - mBorder) / (MONTH_DAY_GAP + mCellWidth);
+ if (column > 6) {
+ column = 6;
+ }
+
+ DayOfMonthCursor c = mCursor;
+ Time time = mTempTime;
+ time.set(mViewCalendar);
+
+ // Compute the day number from the row and column. If the row and
+ // column are in a different month from the current one, then the
+ // monthDay might be negative or it might be greater than the number
+ // of days in this month, but that is okay because the normalize()
+ // method will adjust the month (and year) if necessary.
+ time.monthDay = 7 * row + column - c.getOffset() + 1;
+ return time.normalize(true);
+ }
+
+ /**
+ * Create a bitmap at the origin and draw the drawable to it using the bounds specified by rect.
+ *
+ * @param drawable the drawable we wish to render
+ * @param width the width of the resulting bitmap
+ * @param height the height of the resulting bitmap
+ * @return a new bitmap
+ */
+ private Bitmap createBitmap(Drawable drawable, int width, int height) {
+ // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888)
+ Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
+
+ // Draw the drawable into the bitmap at the origin.
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, width, height);
+ drawable.draw(canvas);
+ return bitmap;
+ }
+
+ /**
+ * Clears the bitmap cache. Generally only needed when the screen size changed.
+ */
+ private void clearBitmapCache() {
+ recycleAndClearBitmapCache(mEventBitmapCache);
+ recycleAndClearBitmapCache(mDayBitmapCache);
+ }
+
+ private void recycleAndClearBitmapCache(SparseArray<Bitmap> bitmapCache) {
+ int size = bitmapCache.size();
+ for(int i = 0; i < size; i++) {
+ bitmapCache.valueAt(i).recycle();
+ }
+ bitmapCache.clear();
+
+ }
+
+ /**
+ * Draw a single box onto the canvas.
+ * @param day The Julian day.
+ * @param weekNum The week number.
+ * @param row The row of the box (0-5).
+ * @param column The column of the box (0-6).
+ * @param canvas The canvas to draw on.
+ * @param p The paint used for drawing.
+ * @param r The rectangle used for each box.
+ * @param isLandscape Is the current orientation landscape.
+ */
+ private void drawBox(int day, int weekNum, int row, int column, Canvas canvas, Paint p,
+ Rect r, boolean isLandscape) {
+
+ // Only draw the selection if we are in the press state or if we have
+ // moved the cursor with key input.
+ boolean drawSelection = false;
+ if (mSelectionMode != SELECTION_HIDDEN) {
+ drawSelection = mCursor.isSelected(row, column);
+ }
+
+ boolean withinCurrentMonth = mCursor.isWithinCurrentMonth(row, column);
+ boolean isToday = false;
+ int dayOfBox = mCursor.getDayAt(row, column);
+ if (dayOfBox == mToday.monthDay && mCursor.getYear() == mToday.year
+ && mCursor.getMonth() == mToday.month) {
+ isToday = true;
+ }
+
+ int y = WEEK_GAP + row*(WEEK_GAP + mCellHeight);
+ int x = mBorder + column*(MONTH_DAY_GAP + mCellWidth);
+
+ r.left = x;
+ r.top = y;
+ r.right = x + mCellWidth;
+ r.bottom = y + mCellHeight;
+
+
+ // Adjust the left column, right column, and bottom row to leave
+ // no border.
+ if (column == 0) {
+ r.left = -1;
+ } else if (column == 6) {
+ r.right += mBorder + 2;
+ }
+
+ if (row == 5) {
+ r.bottom = getMeasuredHeight();
+ }
+
+ // Draw the cell contents (excluding monthDay number)
+ if (!withinCurrentMonth) {
+ boolean firstDayOfNextmonth = isFirstDayOfNextMonth(row, column);
+
+ // Adjust cell boundaries to compensate for the different border
+ // style.
+ r.top--;
+ if (column != 0) {
+ r.left--;
+ }
+
+ // Draw cell border
+ p.setColor(mMonthOtherMonthColor);
+ p.setAntiAlias(false);
+
+ if (row == 0) {
+ // Bottom line
+ canvas.drawLine(r.left, r.bottom, r.right, r.bottom, p);
+ }
+
+ // Top line
+ canvas.drawLine(r.left, r.top, r.right, r.top, p);
+
+ // Right line
+ canvas.drawLine(r.right, r.top, r.right, r.bottom, p);
+
+ if (firstDayOfNextmonth && column != 0) {
+ canvas.drawLine(r.left, r.top, r.left, r.bottom, p);
+ }
+ } else if (drawSelection) {
+ if (mSelectionMode == SELECTION_SELECTED) {
+ mBoxSelected.setBounds(r);
+ mBoxSelected.draw(canvas);
+ } else if (mSelectionMode == SELECTION_PRESSED) {
+ mBoxPressed.setBounds(r);
+ mBoxPressed.draw(canvas);
+ } else {
+ mBoxLongPressed.setBounds(r);
+ mBoxLongPressed.draw(canvas);
+ }
+
+ drawEvents(day, canvas, r, p);
+ if (!mAnimating) {
+ updateEventDetails(day);
+ }
+ } else {
+ // Today gets a different background
+ if (isToday) {
+ // We could cache this for a little bit more performance, but it's not on the
+ // performance radar...
+ Drawable background = mTodayBackground;
+ background.setBounds(r);
+ background.draw(canvas);
+ } else {
+ // Use the bitmap cache to draw the day background
+ int width = r.right - r.left;
+ int height = r.bottom - r.top;
+ // Compute a unique id that depends on width and height.
+ int id = (height << MODULO_SHIFT) | width;
+ Bitmap bitmap = mDayBitmapCache.get(id);
+ if (bitmap == null) {
+ bitmap = createBitmap(mDayBackground, width, height);
+ mDayBitmapCache.put(id, bitmap);
+ }
+ canvas.drawBitmap(bitmap, r.left, r.top, p);
+ }
+ drawEvents(day, canvas, r, p);
+ }
+
+ // Draw week number
+ if (mShowWeekNumbers && column == 0) {
+ // Draw the banner
+ p.setStyle(Paint.Style.FILL);
+ p.setColor(mMonthWeekBannerColor);
+ int right = r.right;
+ r.right = right - BUSYBIT_WIDTH - BUSYBIT_RIGHT_MARGIN;
+ if (isLandscape) {
+ int bottom = r.bottom;
+ r.bottom = r.top + WEEK_BANNER_HEIGHT;
+ r.left++;
+ canvas.drawRect(r, p);
+ r.bottom = bottom;
+ r.left--;
+ } else {
+ int top = r.top;
+ r.top = r.bottom - WEEK_BANNER_HEIGHT;
+ r.left++;
+ canvas.drawRect(r, p);
+ r.top = top;
+ r.left--;
+ }
+ r.right = right;
+
+ // Draw the number
+ p.setColor(mMonthOtherMonthBannerColor);
+ p.setAntiAlias(true);
+ p.setTypeface(null);
+ p.setTextSize(WEEK_TEXT_SIZE);
+ p.setTextAlign(Paint.Align.LEFT);
+
+ int textX = r.left + WEEK_TEXT_PADDING;
+ int textY;
+ if (isLandscape) {
+ textY = r.top + WEEK_BANNER_HEIGHT - WEEK_TEXT_PADDING;
+ } else {
+ textY = r.bottom - WEEK_TEXT_PADDING;
+ }
+
+ canvas.drawText(String.valueOf(weekNum), textX, textY, p);
+ }
+
+ // Draw the monthDay number
+ p.setStyle(Paint.Style.FILL);
+ p.setAntiAlias(true);
+ p.setTypeface(null);
+ p.setTextSize(MONTH_DAY_TEXT_SIZE);
+
+ if (!withinCurrentMonth) {
+ p.setColor(mMonthOtherMonthDayNumberColor);
+ } else if (drawSelection || !isToday) {
+ p.setColor(mMonthDayNumberColor);
+ } else {
+ p.setColor(mMonthTodayNumberColor);
+ }
+
+ p.setTextAlign(Paint.Align.CENTER);
+ int right = r.right - BUSYBIT_WIDTH - BUSYBIT_RIGHT_MARGIN;
+ int textX = r.left + (right - r.left) / 2; // center of text
+ int textY = r.bottom - BUSYBIT_TOP_BOTTOM_MARGIN - 2; // bottom of text
+ canvas.drawText(String.valueOf(mCursor.getDayAt(row, column)), textX, textY, p);
+ }
+
+ /**
+ * Converts the busy bits from the database that use 1-hour intervals to
+ * the 4-hour time slots needed in this view. Also, we map all-day
+ * events to the first two 4-hour time slots (that is, an all-day event
+ * will look like the first 8 hours from 12am to 8am are busy). This
+ * looks better than setting just the first 4-hour time slot because that
+ * is barely visible in landscape mode.
+ */
+ private void convertBusyBits() {
+ if (DEBUG_BUSYBITS) {
+ Log.i("Cal", "convertBusyBits() SLOTS_PER_DAY: " + SLOTS_PER_DAY
+ + " BUSY_SLOT_MASK: " + BUSY_SLOT_MASK
+ + " INTERVALS_PER_BUSY_SLOT: " + INTERVALS_PER_BUSY_SLOT);
+ for (int day = 0; day < 31; day++) {
+ int bits = mRawBusyBits[day];
+ String bitString = String.format("0x%06x", bits);
+ String valString = "";
+ for (int slot = 0; slot < SLOTS_PER_DAY; slot++) {
+ int val = bits & BUSY_SLOT_MASK;
+ bits = bits >>> INTERVALS_PER_BUSY_SLOT;
+ valString += " " + val;
+ }
+ Log.i("Cal", "[" + day + "] " + bitString + " " + valString
+ + " allday: " + mAllDayCounts[day]);
+ }
+ }
+ for (int day = 0; day < 31; day++) {
+ int bits = mRawBusyBits[day];
+ for (int slot = 0; slot < SLOTS_PER_DAY; slot++) {
+ int val = bits & BUSY_SLOT_MASK;
+ bits = bits >>> INTERVALS_PER_BUSY_SLOT;
+ if (val == 0) {
+ mBusyBits[day][slot] = 0;
+ } else {
+ mBusyBits[day][slot] = 1;
+ }
+ }
+ if (mAllDayCounts[day] > 0) {
+ mBusyBits[day][0] = 1;
+ mBusyBits[day][1] = 1;
+ }
+ }
+ }
+
+ /**
+ * Create a bitmap at the origin for the given set of busyBits.
+ *
+ * @param busyBits an array of bits with elements set to 1 if we have an event for that slot
+ * @param rect the size of the resulting
+ * @return a new bitmap
+ */
+ private Bitmap createEventBitmap(byte[] busyBits, Rect rect) {
+ // Compute the size of the smallest bitmap, excluding margins.
+ final int left = 0;
+ final int right = BUSYBIT_WIDTH;
+ final int top = 0;
+ final int bottom = (rect.bottom - rect.top) - 2 * BUSYBIT_TOP_BOTTOM_MARGIN;
+ final int height = bottom - top;
+ final int width = right - left;
+
+ final Drawable dnaEmpty = mDnaEmpty;
+ final Drawable dnaTop = mDnaTop;
+ final Drawable dnaMiddle = mDnaMiddle;
+ final Drawable dnaBottom = mDnaBottom;
+ final float slotHeight = (float) height / SLOTS_PER_DAY;
+
+ // Create a bitmap with the same format as mBitmap (should be Bitmap.Config.ARGB_8888)
+ Bitmap bitmap = Bitmap.createBitmap(width, height, mBitmap.getConfig());
+
+ // Create a canvas for drawing and draw background (dnaEmpty)
+ Canvas canvas = new Canvas(bitmap);
+ dnaEmpty.setBounds(left, top, right, bottom);
+ dnaEmpty.draw(canvas);
+
+ // The first busy bit is a drawable that is round at the top
+ if (busyBits[0] == 1) {
+ float rectBottom = top + slotHeight;
+ dnaTop.setBounds(left, top, right, (int) rectBottom);
+ dnaTop.draw(canvas);
+ }
+
+ // The last busy bit is a drawable that is round on the bottom
+ int lastIndex = busyBits.length - 1;
+ if (busyBits[lastIndex] == 1) {
+ float rectTop = bottom - slotHeight;
+ dnaBottom.setBounds(left, (int) rectTop, right, bottom);
+ dnaBottom.draw(canvas);
+ }
+
+ // Draw all intermediate pieces. We could further optimize this to
+ // draw runs of bits, but it probably won't yield much more performance.
+ float rectTop = top + slotHeight;
+ for (int index = 1; index < lastIndex; index++) {
+ float rectBottom = rectTop + slotHeight;
+ if (busyBits[index] == 1) {
+ dnaMiddle.setBounds(left, (int) rectTop, right, (int) rectBottom);
+ dnaMiddle.draw(canvas);
+ }
+ rectTop = rectBottom;
+ }
+ return bitmap;
+ }
+
+ private void drawEvents(int date, Canvas canvas, Rect rect, Paint p) {
+ // These are the coordinates of the upper left corner where we'll draw the event bitmap
+ int top = rect.top + BUSYBIT_TOP_BOTTOM_MARGIN;
+ int right = rect.right - BUSYBIT_RIGHT_MARGIN;
+ int left = right - BUSYBIT_WIDTH;
+
+ // Display the busy bits. Draw a rectangle for each run of 1-bits.
+ int day = date - mFirstJulianDay;
+ byte[] busyBits = mBusyBits[day];
+ int lastIndex = busyBits.length - 1;
+
+ // Cache index is simply all of the bits combined into an integer
+ int cacheIndex = 0;
+ for (int i = 0 ; i <= lastIndex; i++) cacheIndex |= busyBits[i] << i;
+ Bitmap bitmap = mEventBitmapCache.get(cacheIndex);
+ if (bitmap == null) {
+ // Create a bitmap that we'll reuse for all events with the same
+ // combination of busyBits.
+ bitmap = createEventBitmap(busyBits, rect);
+ mEventBitmapCache.put(cacheIndex, bitmap);
+ }
+ canvas.drawBitmap(bitmap, left, top, p);
+ }
+
+ private boolean isFirstDayOfNextMonth(int row, int column) {
+ if (column == 0) {
+ column = 6;
+ row--;
+ } else {
+ column--;
+ }
+ return mCursor.isWithinCurrentMonth(row, column);
+ }
+
+ private int getWeekOfYear(int row, int column, boolean isWithinCurrentMonth,
+ Calendar calendar) {
+ calendar.set(Calendar.DAY_OF_MONTH, mCursor.getDayAt(row, column));
+ if (isWithinCurrentMonth) {
+ calendar.set(Calendar.MONTH, mCursor.getMonth());
+ calendar.set(Calendar.YEAR, mCursor.getYear());
+ } else {
+ int month = mCursor.getMonth();
+ int year = mCursor.getYear();
+ if (row < 2) {
+ // Previous month
+ if (month == 0) {
+ year--;
+ month = 11;
+ } else {
+ month--;
+ }
+ } else {
+ // Next month
+ if (month == 11) {
+ year++;
+ month = 0;
+ } else {
+ month++;
+ }
+ }
+ calendar.set(Calendar.MONTH, month);
+ calendar.set(Calendar.YEAR, year);
+ }
+
+ return calendar.get(Calendar.WEEK_OF_YEAR);
+ }
+
+ void setDetailedView(String detailedView) {
+ mDetailedView = detailedView;
+ }
+
+ void setSelectedTime(Time time) {
+ // Save the selected time so that we can restore it later when we switch views.
+ mSavedTime.set(time);
+
+ mViewCalendar.set(time);
+ mViewCalendar.monthDay = 1;
+ long millis = mViewCalendar.normalize(true /* ignore DST */);
+ mFirstJulianDay = Time.getJulianDay(millis, mViewCalendar.gmtoff);
+ mViewCalendar.set(time);
+
+ mCursor = new DayOfMonthCursor(time.year, time.month, time.monthDay,
+ mCursor.getWeekStartDay());
+
+ mRedrawScreen = true;
+ invalidate();
+ }
+
+ public long getSelectedTimeInMillis() {
+ Time time = mTempTime;
+ time.set(mViewCalendar);
+
+ time.monthDay = mCursor.getSelectedDayOfMonth();
+
+ // Restore the saved hour:minute:second offset from when we entered
+ // this view.
+ time.second = mSavedTime.second;
+ time.minute = mSavedTime.minute;
+ time.hour = mSavedTime.hour;
+ return time.normalize(true);
+ }
+
+ Time getTime() {
+ return mViewCalendar;
+ }
+
+ public int getSelectionMode() {
+ return mSelectionMode;
+ }
+
+ public void setSelectionMode(int selectionMode) {
+ mSelectionMode = selectionMode;
+ }
+
+ private void drawingCalc(int width, int height) {
+ mCellHeight = (height - (6 * WEEK_GAP)) / 6;
+ mEventGeometry.setHourHeight((mCellHeight - 25.0f * HOUR_GAP) / 24.0f);
+ mCellWidth = (width - (6 * MONTH_DAY_GAP)) / 7;
+ mBorder = (width - 6 * (mCellWidth + MONTH_DAY_GAP) - mCellWidth) / 2;
+
+ if (mShowToast) {
+ mPopup.dismiss();
+ mPopup.setWidth(width - 20);
+ mPopup.setHeight(POPUP_HEIGHT);
+ }
+
+ if (((mBitmap == null)
+ || mBitmap.isRecycled()
+ || (mBitmap.getHeight() != height)
+ || (mBitmap.getWidth() != width))
+ && (width > 0) && (height > 0)) {
+ if (mBitmap != null) {
+ mBitmap.recycle();
+ }
+ mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ }
+
+ mBitmapRect.top = 0;
+ mBitmapRect.bottom = height;
+ mBitmapRect.left = 0;
+ mBitmapRect.right = width;
+ }
+
+ private void updateEventDetails(int date) {
+ if (!mShowToast) {
+ return;
+ }
+
+ getHandler().removeCallbacks(mDismissPopup);
+ ArrayList<Event> events = mEvents;
+ int numEvents = events.size();
+ if (numEvents == 0) {
+ mPopup.dismiss();
+ return;
+ }
+
+ int eventIndex = 0;
+ for (int i = 0; i < numEvents; i++) {
+ Event event = events.get(i);
+
+ if (event.startDay > date || event.endDay < date) {
+ continue;
+ }
+
+ // If we have all the event that we can display, then just count
+ // the extra ones.
+ if (eventIndex >= 4) {
+ eventIndex += 1;
+ continue;
+ }
+
+ int flags;
+ boolean showEndTime = false;
+ if (event.allDay) {
+ int numDays = event.endDay - event.startDay;
+ if (numDays == 0) {
+ flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
+ } else {
+ showEndTime = true;
+ flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE
+ | DateUtils.FORMAT_ABBREV_ALL;
+ }
+ } else {
+ flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
+ if (DateFormat.is24HourFormat(mContext)) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+ }
+
+ String timeRange;
+ if (showEndTime) {
+ timeRange = DateUtils.formatDateRange(event.startMillis, event.endMillis, flags);
+ } else {
+ timeRange = DateUtils.formatDateRange(event.startMillis, event.startMillis, flags);
+ }
+
+ TextView timeView = null;
+ TextView titleView = null;
+ switch (eventIndex) {
+ case 0:
+ timeView = (TextView) mPopupView.findViewById(R.id.time0);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title0);
+ break;
+ case 1:
+ timeView = (TextView) mPopupView.findViewById(R.id.time1);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title1);
+ break;
+ case 2:
+ timeView = (TextView) mPopupView.findViewById(R.id.time2);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title2);
+ break;
+ case 3:
+ timeView = (TextView) mPopupView.findViewById(R.id.time3);
+ titleView = (TextView) mPopupView.findViewById(R.id.event_title3);
+ break;
+ }
+
+ timeView.setText(timeRange);
+ titleView.setText(event.title);
+ eventIndex += 1;
+ }
+ if (eventIndex == 0) {
+ // We didn't find any events for this day
+ mPopup.dismiss();
+ return;
+ }
+
+ // Hide the items that have no event information
+ View view;
+ switch (eventIndex) {
+ case 1:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ case 2:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ case 3:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.GONE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ case 4:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.plus_more);
+ view.setVisibility(View.GONE);
+ break;
+ default:
+ view = mPopupView.findViewById(R.id.item_layout1);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout2);
+ view.setVisibility(View.VISIBLE);
+ view = mPopupView.findViewById(R.id.item_layout3);
+ view.setVisibility(View.VISIBLE);
+ TextView tv = (TextView) mPopupView.findViewById(R.id.plus_more);
+ tv.setVisibility(View.VISIBLE);
+ String format = mResources.getString(R.string.plus_N_more);
+ String plusMore = String.format(format, eventIndex - 4);
+ tv.setText(plusMore);
+ break;
+ }
+
+ if (eventIndex > 5) {
+ eventIndex = 5;
+ }
+ int popupHeight = 20 * eventIndex + 15;
+ mPopup.setHeight(popupHeight);
+
+ if (mPreviousPopupHeight != popupHeight) {
+ mPreviousPopupHeight = popupHeight;
+ mPopup.dismiss();
+ }
+ mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, 0, 0);
+ postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ long duration = event.getEventTime() - event.getDownTime();
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (mSelectionMode == SELECTION_HIDDEN) {
+ // Don't do anything unless the selection is visible.
+ break;
+ }
+
+ if (mSelectionMode == SELECTION_PRESSED) {
+ // This was the first press when there was nothing selected.
+ // Change the selection from the "pressed" state to the
+ // the "selected" state. We treat short-press and
+ // long-press the same here because nothing was selected.
+ mSelectionMode = SELECTION_SELECTED;
+ mRedrawScreen = true;
+ invalidate();
+ break;
+ }
+
+ // Check the duration to determine if this was a short press
+ if (duration < ViewConfiguration.getLongPressTimeout()) {
+ long millis = getSelectedTimeInMillis();
+ Utils.startActivity(getContext(), mDetailedView, millis);
+ mParentActivity.finish();
+ } else {
+ mSelectionMode = SELECTION_LONGPRESS;
+ mRedrawScreen = true;
+ invalidate();
+ performLongClick();
+ }
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mSelectionMode == SELECTION_HIDDEN) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+ || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
+ || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ // Display the selection box but don't move or select it
+ // on this key press.
+ mSelectionMode = SELECTION_SELECTED;
+ mRedrawScreen = true;
+ invalidate();
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ // Display the selection box but don't select it
+ // on this key press.
+ mSelectionMode = SELECTION_PRESSED;
+ mRedrawScreen = true;
+ invalidate();
+ return true;
+ }
+ }
+
+ mSelectionMode = SELECTION_SELECTED;
+ boolean redraw = false;
+ Time other = null;
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ENTER:
+ long millis = getSelectedTimeInMillis();
+ Utils.startActivity(getContext(), mDetailedView, millis);
+ mParentActivity.finish();
+ return true;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (mCursor.up()) {
+ other = mOtherViewCalendar;
+ other.set(mViewCalendar);
+ other.month -= 1;
+ other.monthDay = mCursor.getSelectedDayOfMonth();
+
+ // restore the calendar cursor for the animation
+ mCursor.down();
+ }
+ redraw = true;
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (mCursor.down()) {
+ other = mOtherViewCalendar;
+ other.set(mViewCalendar);
+ other.month += 1;
+ other.monthDay = mCursor.getSelectedDayOfMonth();
+
+ // restore the calendar cursor for the animation
+ mCursor.up();
+ }
+ redraw = true;
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (mCursor.left()) {
+ other = mOtherViewCalendar;
+ other.set(mViewCalendar);
+ other.month -= 1;
+ other.monthDay = mCursor.getSelectedDayOfMonth();
+
+ // restore the calendar cursor for the animation
+ mCursor.right();
+ }
+ redraw = true;
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (mCursor.right()) {
+ other = mOtherViewCalendar;
+ other.set(mViewCalendar);
+ other.month += 1;
+ other.monthDay = mCursor.getSelectedDayOfMonth();
+
+ // restore the calendar cursor for the animation
+ mCursor.left();
+ }
+ redraw = true;
+ break;
+ }
+
+ if (other != null) {
+ other.normalize(true /* ignore DST */);
+ mNavigator.goTo(other);
+ } else if (redraw) {
+ mRedrawScreen = true;
+ invalidate();
+ }
+
+ return redraw;
+ }
+
+ class DismissPopup implements Runnable {
+ public void run() {
+ mPopup.dismiss();
+ }
+ }
+
+ // This is called when the activity is paused so that the popup can
+ // be dismissed.
+ void dismissPopup() {
+ if (!mShowToast) {
+ return;
+ }
+
+ // Protect against null-pointer exceptions
+ if (mPopup != null) {
+ mPopup.dismiss();
+ }
+
+ Handler handler = getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(mDismissPopup);
+ }
+ }
+}