DayView.java revision 000ba1109e2f4e527e9227fe341598ceeb3fbca6
15f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer/*
25f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * Copyright (C) 2007 The Android Open Source Project
35f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *
45f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * Licensed under the Apache License, Version 2.0 (the "License");
50bc735ffcfb223c0186419547abaa5c84482663eChris Lattner * you may not use this file except in compliance with the License.
60bc735ffcfb223c0186419547abaa5c84482663eChris Lattner * You may obtain a copy of the License at
75f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *
85f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *      http://www.apache.org/licenses/LICENSE-2.0
95f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer *
105f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * Unless required by applicable law or agreed to in writing, software
115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * distributed under the License is distributed on an "AS IS" BASIS,
125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * See the License for the specific language governing permissions and
145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer * limitations under the License.
155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer */
165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
177573098b83e780d1c5bea13b384b610d8f155676Steve Naroffpackage com.android.calendar;
18a95d3750441ac8ad03e36af8e6e74039c9a3109dTed Kremenek
195f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport com.android.calendar.CalendarController.EventType;
209caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenekimport com.android.calendar.CalendarController.ViewType;
218ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek
225f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.content.ContentResolver;
23525204a7ca5c3c0aac8166d8f27abb988a84c850Anton Korobeynikovimport android.content.ContentUris;
240a449eed1dd2439b4b9c0a6291084816eab390c1Ted Kremenekimport android.content.Context;
258189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenekimport android.content.res.Resources;
266e340496341a4704be0ede9c1ff4f8eacea7ee2cChris Lattnerimport android.content.res.TypedArray;
277573098b83e780d1c5bea13b384b610d8f155676Steve Naroffimport android.database.Cursor;
287573098b83e780d1c5bea13b384b610d8f155676Steve Naroffimport android.graphics.Canvas;
295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.graphics.Paint;
30e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishopimport android.graphics.Paint.Style;
315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.graphics.Rect;
325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.graphics.Typeface;
337ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroffimport android.graphics.drawable.Drawable;
344b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redlimport android.net.Uri;
355f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencerimport android.os.Handler;
360c727a35718556866a978f64ac549d9798735f08Chris Lattnerimport android.provider.Calendar.Attendees;
376a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlssonimport android.provider.Calendar.Calendars;
386c36be5b383875b490684bcf439d6d427298c1afChris Lattnerimport android.provider.Calendar.Events;
3942a509f6a4f71bb805cc4abbb26722a34dffdddeTed Kremenekimport android.text.Layout.Alignment;
40ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.text.StaticLayout;
41ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.text.TextPaint;
42ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.text.TextUtils;
43ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.text.format.DateFormat;
44ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.text.format.DateUtils;
45ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.text.format.Time;
46ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.util.Log;
47ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.ContextMenu;
48ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.ContextMenu.ContextMenuInfo;
49ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.GestureDetector;
50ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.Gravity;
51ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.KeyEvent;
52ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.LayoutInflater;
53ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.MenuItem;
54ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.MotionEvent;
55ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.ScaleGestureDetector;
56ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.View;
57ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.ViewConfiguration;
58ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.ViewGroup;
59ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.WindowManager;
60ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.animation.Animation;
61ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.view.animation.TranslateAnimation;
62ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.widget.ImageView;
63ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.widget.PopupWindow;
64ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.widget.TextView;
65ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport android.widget.ViewSwitcher;
66ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
67ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport java.util.ArrayList;
68ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport java.util.Arrays;
69ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport java.util.Calendar;
70ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport java.util.regex.Matcher;
71ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekimport java.util.regex.Pattern;
72ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
73ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek/**
74ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek * View for multi-day view. So far only 1 and 7 day have been tested.
75ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek */
76ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenekpublic class DayView extends View implements View.OnCreateContextMenuListener,
77ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        ScaleGestureDetector.OnScaleGestureListener, View.OnClickListener {
78ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static String TAG = "DayView";
79ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static boolean DEBUG = false;
80ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
81ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static float mScale = 0; // Used for supporting different screen densities
82ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final long INVALID_EVENT_ID = -1; //This is used for remembering a null event
83ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final long ANIMATION_DURATION = 400;
84ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
85ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final int MENU_AGENDA = 2;
86ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final int MENU_DAY = 3;
87ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final int MENU_EVENT_VIEW = 5;
88ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final int MENU_EVENT_CREATE = 6;
89ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final int MENU_EVENT_EDIT = 7;
90ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    private static final int MENU_EVENT_DELETE = 8;
915f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int DEFAULT_CELL_HEIGHT = 64;
935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int MAX_CELL_HEIGHT = 150;
945f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int MIN_Y_SPAN = 100;
955f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
96f2cad8633e46cce12fc3d77c0bd451ffd7264bafDouglas Gregor    private boolean mOnFlingCalled;
97f2cad8633e46cce12fc3d77c0bd451ffd7264bafDouglas Gregor    /**
98f2cad8633e46cce12fc3d77c0bd451ffd7264bafDouglas Gregor     * ID of the last event which was displayed with the toast popup.
99f2cad8633e46cce12fc3d77c0bd451ffd7264bafDouglas Gregor     *
100f2cad8633e46cce12fc3d77c0bd451ffd7264bafDouglas Gregor     * This is used to prevent popping up multiple quick views for the same event, especially
101f2cad8633e46cce12fc3d77c0bd451ffd7264bafDouglas Gregor     * during calendar syncs. This becomes valid when an event is selected, either by default
1025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * on starting calendar or by scrolling to an event. It becomes invalid when the user
1035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * explicitly scrolls to an empty time slot, changes views, or deletes the event.
1045f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
1055f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private long mLastPopupEventID;
1068189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
1078189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    protected Context mContext;
1088189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
1098189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final String[] CALENDARS_PROJECTION = new String[] {
1108189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek        Calendars._ID,          // 0
1118189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek        Calendars.ACCESS_LEVEL, // 1
1128189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek        Calendars.OWNER_ACCOUNT, // 2
1138189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    };
1148189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int CALENDARS_INDEX_ACCESS_LEVEL = 1;
1158189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int CALENDARS_INDEX_OWNER_ACCOUNT = 2;
1168189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final String CALENDARS_WHERE = Calendars._ID + "=%d";
1178189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
1188189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final String[] ATTENDEES_PROJECTION = new String[] {
1198189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek        Attendees._ID,                      // 0
1208189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek        Attendees.ATTENDEE_RELATIONSHIP,    // 1
1218189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    };
1228189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int ATTENDEES_INDEX_RELATIONSHIP = 1;
1238189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=%d";
1248189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
1258189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int FROM_NONE = 0;
1268189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int FROM_ABOVE = 1;
1278189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int FROM_BELOW = 2;
1288189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int FROM_LEFT = 4;
1298189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int FROM_RIGHT = 8;
1308189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
1318189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int ACCESS_LEVEL_NONE = 0;
1328189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int ACCESS_LEVEL_DELETE = 1;
133e2dedf8f61b8f306f704781456b482eb61871e8eDouglas Gregor    private static final int ACCESS_LEVEL_EDIT = 2;
134fb523e16dd1f860ff02a3ae03e5e3e25327a5860Chris Lattner
135fb523e16dd1f860ff02a3ae03e5e3e25327a5860Chris Lattner    private static int mHorizontalSnapBackThreshold = 128;
136e2dedf8f61b8f306f704781456b482eb61871e8eDouglas Gregor    private static int HORIZONTAL_FLING_THRESHOLD = 75;
137fb523e16dd1f860ff02a3ae03e5e3e25327a5860Chris Lattner
138e2dedf8f61b8f306f704781456b482eb61871e8eDouglas Gregor    private ContinueScroll mContinueScroll = new ContinueScroll();
139025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
140025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    // Make this visible within the package for more informative debugging
141025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    Time mBaseDate;
142025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private Time mCurrentTime;
143025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    //Update the current time line every five minutes if the window is left open that long
144025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private static final int UPDATE_CURRENT_TIME_DELAY = 300000;
1459c1863ef36a74e8203f00289d19856ad956f48b9Ted Kremenek    private UpdateCurrentTime mUpdateCurrentTime = new UpdateCurrentTime();
1469c1863ef36a74e8203f00289d19856ad956f48b9Ted Kremenek    private int mTodayJulianDay;
1479c1863ef36a74e8203f00289d19856ad956f48b9Ted Kremenek
14827f8a28bee33bb0e857cfe1a61c281bbc234b338Ted Kremenek    private Typeface mBold = Typeface.DEFAULT_BOLD;
1499c1863ef36a74e8203f00289d19856ad956f48b9Ted Kremenek    private int mFirstJulianDay;
150025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private int mLastJulianDay;
151025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
152025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private int mMonthLength;
153025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private int mFirstVisibleDate;
154025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private int mFirstVisibleDayOfWeek;
1555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int[] mEarliestStartHour;    // indexed by the week day offset
1565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private boolean[] mHasAllDayEvent;   // indexed by the week day offset
1575f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
1585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private Runnable mTZUpdater = new Runnable() {
1595f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        @Override
16027f8a28bee33bb0e857cfe1a61c281bbc234b338Ted Kremenek        public void run() {
16124e1e707b4c362f18e371e2bbf054a8345b57bfaChris Lattner            String tz = Utils.getTimeZone(mContext, this);
1625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mBaseDate.timezone = tz;
1635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mBaseDate.normalize(true);
1645f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mCurrentTime.switchTimezone(tz);
165b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            invalidate();
166b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        }
167b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    };
168b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
169b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    /**
170311ff02fae0392bee6abe7723cdf5a69b2899a47Chris Lattner     * This variable helps to avoid unnecessarily reloading events by keeping
171311ff02fae0392bee6abe7723cdf5a69b2899a47Chris Lattner     * track of the start millis parameter used for the most recent loading
1725f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * of events.  If the next reload matches this, then the events are not
1735f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * reloaded.  To force a reload, set this to zero (this is set to zero
1745f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * in the method clearCachedEvents()).
1755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
1765f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private long mLastReloadMillis;
1776000dace22f110d8768476989313e9d981d690d0Chris Lattner
1786000dace22f110d8768476989313e9d981d690d0Chris Lattner    private ArrayList<Event> mEvents = new ArrayList<Event>();
1796000dace22f110d8768476989313e9d981d690d0Chris Lattner    private ArrayList<Event> mAllDayEvents = new ArrayList<Event>();
1806000dace22f110d8768476989313e9d981d690d0Chris Lattner    private StaticLayout[] mLayouts = null;
1815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private StaticLayout[] mAllDayLayouts = null;
182e300c870f08d08badf2ebcb53ded49f304af37fcChris Lattner    private int mSelectionDay;        // Julian day
1836000dace22f110d8768476989313e9d981d690d0Chris Lattner    private int mSelectionHour;
1846000dace22f110d8768476989313e9d981d690d0Chris Lattner
1856000dace22f110d8768476989313e9d981d690d0Chris Lattner    boolean mSelectionAllDay;
186e300c870f08d08badf2ebcb53ded49f304af37fcChris Lattner
1876000dace22f110d8768476989313e9d981d690d0Chris Lattner    /** Width of a day or non-conflicting event */
1886000dace22f110d8768476989313e9d981d690d0Chris Lattner    private int mCellWidth;
1896000dace22f110d8768476989313e9d981d690d0Chris Lattner
1906000dace22f110d8768476989313e9d981d690d0Chris Lattner    // Pre-allocate these objects and re-use them
191071e4da10b07ca5278a7ce9dcabd853e0d57e5a4Mike Stump    private Rect mRect = new Rect();
192071e4da10b07ca5278a7ce9dcabd853e0d57e5a4Mike Stump    private Rect mDestRect = new Rect();
1935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private Paint mPaint = new Paint();
194d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek    private Paint mEventTextPaint = new Paint();
195d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek    private Paint mSelectionPaint = new Paint();
196d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek    private float[] mLines;
197d2a4a1af1088fca80e2dc76eb3369db0fbbfdefdTed Kremenek
1985f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mFirstDayOfWeek; // First day of the week
1998297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
20077ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private PopupWindow mPopup;
201d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private View mPopupView;
202d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek
203d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    // The number of milliseconds to show the popup window
204d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private static final int POPUP_DISMISS_DELAY = 3000;
205d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private DismissPopup mDismissPopup = new DismissPopup();
206d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek
207d48ade633d96b94cb435d73e2c935ea457152decTed Kremenek    private boolean mRemeasure = true;
20877ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek
20977ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private final EventLoader mEventLoader;
21077ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    protected final EventGeometry mEventGeometry;
2119caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek
2129caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek    private static final float GRID_LINE_INNER_WIDTH = 1;
21377ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private static final float GRID_LINE_WIDTH = 5;
2148297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
2158297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private static final int DAY_GAP = 1;
21677ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private static final int HOUR_GAP = 1;
21777ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private static int SINGLE_ALLDAY_HEIGHT = 34;
2189caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek    private static int MAX_ALLDAY_HEIGHT = 105;
21977ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private static int ALLDAY_TOP_MARGIN = 3;
22077ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private static int MAX_HEIGHT_OF_ONE_ALLDAY_EVENT = 34;
22177ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek
2229caf8b1ca6beb254f420dada3c0e94d5ef027f58Ted Kremenek    private static final int HOURS_LEFT_MARGIN = 2;
22377ed8e4edf6ed78c53fb20ec3210aff2a59c9d87Ted Kremenek    private static final int HOURS_RIGHT_MARGIN = 4;
2240b7489194f9f89fac39d57211c1e7953ae50251fDouglas Gregor    private static final int HOURS_MARGIN = HOURS_LEFT_MARGIN + HOURS_RIGHT_MARGIN;
2252dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek
226e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop    private static int CURRENT_TIME_LINE_HEIGHT = 2;
227e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop    private static int CURRENT_TIME_LINE_BORDER_WIDTH = 1;
228ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    private static final int CURRENT_TIME_LINE_SIDE_BUFFER = 2;
2292dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek
2302dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek    /* package */ static final int MINUTES_PER_HOUR = 60;
2312dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek    /* package */ static final int MINUTES_PER_DAY = MINUTES_PER_HOUR * 24;
2325f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /* package */ static final int MILLIS_PER_MINUTE = 60 * 1000;
2335f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /* package */ static final int MILLIS_PER_HOUR = (3600 * 1000);
2345f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /* package */ static final int MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;
2355f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
2365f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static final int DAY_HEADER_ALPHA = 0x26000000;
2375f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static final int DAY_HEADER_TODAY_ALPHA = 0x99000000;
2385f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static float DAY_HEADER_ONE_DAY_LEFT_MARGIN = -12;
2395f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static float DAY_HEADER_ONE_DAY_RIGHT_MARGIN = 5;
2409653db7bd0e3665b955a0445859285f2e1e7dacdDouglas Gregor    private static float DAY_HEADER_ONE_DAY_BOTTOM_MARGIN = 6;
24181c018d9482e7cc2addadc6202dcf162a01faefdChris Lattner    private static float DAY_HEADER_LEFT_MARGIN = 5;
2425f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static float DAY_HEADER_RIGHT_MARGIN = 7;
2439653db7bd0e3665b955a0445859285f2e1e7dacdDouglas Gregor    private static float DAY_HEADER_BOTTOM_MARGIN = 3;
2448ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek    private static float DAY_HEADER_FONT_SIZE = 14;
2458ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek    private static float DATE_HEADER_FONT_SIZE = 24;
2465f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static float NORMAL_FONT_SIZE = 12;
24784f2170062014d268951902164bed0d8bdea0e82Douglas Gregor    private static float EVENT_TEXT_FONT_SIZE = 12;
24884f2170062014d268951902164bed0d8bdea0e82Douglas Gregor    private static float HOURS_FONT_SIZE = 12;
24984f2170062014d268951902164bed0d8bdea0e82Douglas Gregor    private static float AMPM_FONT_SIZE = 9;
2508e355f296a3ba45b94de49dbb18ae6a6e67df04cTed Kremenek    private static int MIN_CELL_WIDTH_FOR_TEXT = 27;
2518e355f296a3ba45b94de49dbb18ae6a6e67df04cTed Kremenek    private static final int MAX_EVENT_TEXT_LEN = 500;
2527e24e82a70a2c681f4291a3397bcd1e1005f251aChris Lattner    private static float MIN_EVENT_HEIGHT = 15.0F;  // in pixels
253fe95deaf66e4fbd82d44b5f6afa8162fa69cb85cChris Lattner    private static int CALENDAR_COLOR_SQUARE_SIZE = 10;
2547e24e82a70a2c681f4291a3397bcd1e1005f251aChris Lattner    private static int CALENDAR_COLOR_SQUARE_V_OFFSET = -1;
255fe95deaf66e4fbd82d44b5f6afa8162fa69cb85cChris Lattner    private static int CALENDAR_COLOR_SQUARE_H_OFFSET = -3;
2568ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek    private static int EVENT_RECT_TOP_MARGIN = -1;
257655137a6c19110620d82d5f03cdcaec3a48c059fTed Kremenek    private static int EVENT_RECT_BOTTOM_MARGIN = -1;
2587e24e82a70a2c681f4291a3397bcd1e1005f251aChris Lattner    private static int EVENT_RECT_LEFT_MARGIN = -1;
2597e24e82a70a2c681f4291a3397bcd1e1005f251aChris Lattner    private static int EVENT_RECT_RIGHT_MARGIN = -1;
260e66a8cf9117e5fb95a05ff76ec06615e63dd5adeChris Lattner    private static int EVENT_TEXT_TOP_MARGIN = 2;
261e66a8cf9117e5fb95a05ff76ec06615e63dd5adeChris Lattner    private static int EVENT_TEXT_BOTTOM_MARGIN = 3;
262e66a8cf9117e5fb95a05ff76ec06615e63dd5adeChris Lattner    private static int EVENT_TEXT_LEFT_MARGIN = 11;
26384f2170062014d268951902164bed0d8bdea0e82Douglas Gregor    private static int EVENT_TEXT_RIGHT_MARGIN = 4;
264b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private static int EVENT_ALL_DAY_TEXT_TOP_MARGIN = EVENT_TEXT_TOP_MARGIN;
26581c018d9482e7cc2addadc6202dcf162a01faefdChris Lattner    private static int EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN = EVENT_TEXT_BOTTOM_MARGIN;
26684f2170062014d268951902164bed0d8bdea0e82Douglas Gregor    private static int EVENT_ALL_DAY_TEXT_LEFT_MARGIN = EVENT_TEXT_LEFT_MARGIN;
26781c018d9482e7cc2addadc6202dcf162a01faefdChris Lattner    private static int EVENT_ALL_DAY_TEXT_RIGHT_MARGIN = EVENT_TEXT_RIGHT_MARGIN;
26884f2170062014d268951902164bed0d8bdea0e82Douglas Gregor
26984f2170062014d268951902164bed0d8bdea0e82Douglas Gregor    private static int mSelectionColor;
2708ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek    private static int mPressedColor;
27181c018d9482e7cc2addadc6202dcf162a01faefdChris Lattner    private static int mSelectedEventTextColor;
27281c018d9482e7cc2addadc6202dcf162a01faefdChris Lattner    private static int mEventTextColor;
2735f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int mWeek_saturdayColor;
2745f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int mWeek_sundayColor;
2755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int mCalendarDateBannerTextColor;
2765f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//    private static int mCalendarAllDayBackground;
2775f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int mCalendarAmPmLabel;
2788297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek//    private static int mCalendarDateBannerBackground;
27914f8b4ff660bcaa763974b8d0fae81857c594495Ted Kremenek//    private static int mCalendarDateSelected;
2808297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek//    private static int mCalendarGridAreaBackground;
2818297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private static int mCalendarGridAreaSelected;
2820965f446e7685dc01fc0b5e718610530eed3cc63Ted Kremenek    private static int mCalendarGridLineHorizontalColor;
2830632dd6fe068011af5710c0d6a745724021ff620Chris Lattner    private static int mCalendarGridLineVerticalColor;
2840632dd6fe068011af5710c0d6a745724021ff620Chris Lattner    private static int mCalendarGridLineInnerHorizontalColor;
2858a0d2660b6890ed95d34f37d142c0c52ac490194Ted Kremenek    private static int mCalendarGridLineInnerVerticalColor;
2868ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek//    private static int mCalendarHourBackground;
2878ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek    private static int mCalendarHourLabel;
2888ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek//    private static int mCalendarHourSelected;
2898ffb159441e923322bef6b5dee1aaf24c738d75eTed Kremenek
2908a0d2660b6890ed95d34f37d142c0c52ac490194Ted Kremenek    private int mViewStartX;
29114f8b4ff660bcaa763974b8d0fae81857c594495Ted Kremenek    private int mViewStartY;
292ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    private int mMaxViewStartY;
293e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop    private int mViewHeight;
2945f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mViewWidth;
2955f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mGridAreaHeight;
2965f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int mCellHeight = 0; // shared among all DayViews
2975f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private static int mMinCellHeight = 32;
2985f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mScrollStartY;
2995f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mPreviousDirection;
3005f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
3015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
3025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Vertical distance or span between the two touch points at the start of a
303025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor     * scaling gesture
304025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor     */
305025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private float mStartingSpanY = 0;
3065f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /** Height of 1 hour in pixels at the start of a scaling gesture */
307025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private int mCellHeightBeforeScaleGesture;
308b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    /** The hour at the center two touch points */
309507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff    private float mGestureCenterHour = 0;
3105f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
3115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Flag to decide whether to handle the up event. Cases where up events
3125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * should be ignored are 1) right after a scale gesture and 2) finger was
3135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * down before app launch
3145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
3158297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private boolean mHandleActionUp = true;
3168297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
3178297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private int mHoursTextHeight;
3188297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private int mAllDayHeight;
3194927be6102784fc69c50234f7e2253283b3e99e4Ted Kremenek    private static int DAY_HEADER_HEIGHT = 45;
320ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    /**
321e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop     * Max of all day events in a given day in this view.
3225f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
3235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mMaxAllDayEvents;
3245f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
3255f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    protected int mNumDays = 7;
3265f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mNumHours = 10;
3278189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
3288189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    /** Width of the time line (list of hours) to the left. */
329b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private int mHoursWidth;
3305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private int mDateStrWidth;
3318189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private int mFirstCell;
3328189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private int mFirstHour = -1;
3338189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private int mFirstHourOffset;
334026dc96ac6ece60da5e1b98e2a71bd0ff0939fd8Chris Lattner    private String[] mHourStrs;
335026dc96ac6ece60da5e1b98e2a71bd0ff0939fd8Chris Lattner    private String[] mDayStrs;
336026dc96ac6ece60da5e1b98e2a71bd0ff0939fd8Chris Lattner    private String[] mDayStrs2Letter;
337026dc96ac6ece60da5e1b98e2a71bd0ff0939fd8Chris Lattner    private boolean mIs24HourFormat;
338026dc96ac6ece60da5e1b98e2a71bd0ff0939fd8Chris Lattner
339026dc96ac6ece60da5e1b98e2a71bd0ff0939fd8Chris Lattner    private ArrayList<Event> mSelectedEvents = new ArrayList<Event>();
340026dc96ac6ece60da5e1b98e2a71bd0ff0939fd8Chris Lattner    private boolean mComputeSelectedEvents;
3418189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private Event mSelectedEvent;
342025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private Event mPrevSelectedEvent;
343025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private Rect mPrevBox = new Rect();
344025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    protected final Resources mResources;
345025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    protected final Drawable mCurrentTimeLine;
346025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    protected final Drawable mTodayHeaderDrawable;
347025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    protected final Drawable mEventBoxDrawable;
3488189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private String mAmString;
3498189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private String mPmString;
350025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    private DeleteEventHelper mDeleteEventHelper;
351025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
3528189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private ContextMenuHandler mContextMenuHandler = new ContextMenuHandler();
3538189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
3548189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    ScaleGestureDetector mScaleGestureDetector;
3558189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
3565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
3578189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     * The initial state of the touch mode when we enter this view.
3588189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     */
3598189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int TOUCH_MODE_INITIAL_STATE = 0;
3608189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
3618189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    /**
3628189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     * Indicates we just received the touch event and we are waiting to see if
3638189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     * it is a tap or a scroll gesture.
3648189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     */
3658189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int TOUCH_MODE_DOWN = 1;
3668189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
3678189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    /**
3688189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     * Indicates the touch gesture is a vertical scroll
3694ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek     */
3708189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int TOUCH_MODE_VSCROLL = 0x20;
3718189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
3724ce854736dd196e2304f554ebeac8b43c89cf9e2Ted Kremenek    /**
3738189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     * Indicates the touch gesture is a horizontal scroll
3748189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     */
3758189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private static final int TOUCH_MODE_HSCROLL = 0x40;
376b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
3778189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    private int mTouchMode = TOUCH_MODE_INITIAL_STATE;
3788189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek
3798189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek    /**
3808189cde56b4f6f938cd65f53c932fe1860d0204cTed Kremenek     * The selection modes are HIDDEN, PRESSED, SELECTED, and LONGPRESS.
381b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff     */
382b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private static final int SELECTION_HIDDEN = 0;
383b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    private static final int SELECTION_PRESSED = 1; // D-pad down but not up yet
38454395d440dc82a5e51b945c6c2a7f4bc4bea0358Ted Kremenek    private static final int SELECTION_SELECTED = 2;
385cc326204dd97771c336b9aab3b9963ea30d69c29Ted Kremenek    private static final int SELECTION_LONGPRESS = 3;
386025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
387cc326204dd97771c336b9aab3b9963ea30d69c29Ted Kremenek    private int mSelectionMode = SELECTION_HIDDEN;
388025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
38954395d440dc82a5e51b945c6c2a7f4bc4bea0358Ted Kremenek    private boolean mScrolling = false;
3905f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
3915f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private CalendarController mController;
3925f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private ViewSwitcher mViewSwitcher;
3935f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private GestureDetector mGestureDetector;
3948297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
3958297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    public DayView(Context context, CalendarController controller,
3968297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            ViewSwitcher viewSwitcher, EventLoader eventLoader, int numDays) {
3978297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        super(context);
3982dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek        if (mScale == 0) {
399ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            mScale = getContext().getResources().getDisplayMetrics().density;
400e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop            if (mScale != 1) {
4015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                SINGLE_ALLDAY_HEIGHT *= mScale;
4025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                ALLDAY_TOP_MARGIN *= mScale;
403c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                MAX_HEIGHT_OF_ONE_ALLDAY_EVENT *= mScale;
404c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson
405103fc81f12aa635aa0a573c94b1aceb496b4e587Ted Kremenek                NORMAL_FONT_SIZE *= mScale;
406c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                EVENT_TEXT_FONT_SIZE *= mScale;
407c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                HOURS_FONT_SIZE *= mScale;
408c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                AMPM_FONT_SIZE *= mScale;
409103fc81f12aa635aa0a573c94b1aceb496b4e587Ted Kremenek                MIN_CELL_WIDTH_FOR_TEXT *= mScale;
410d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                MIN_EVENT_HEIGHT *= mScale;
411c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson
412c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                HORIZONTAL_FLING_THRESHOLD *= mScale;
413c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson
414c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                CURRENT_TIME_LINE_HEIGHT *= mScale;
415c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                CURRENT_TIME_LINE_BORDER_WIDTH *= mScale;
416c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson
417c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                MIN_Y_SPAN *= mScale;
418d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                MAX_CELL_HEIGHT *= mScale;
419d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                DEFAULT_CELL_HEIGHT *= mScale;
420b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                DAY_HEADER_HEIGHT *= mScale;
421b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                DAY_HEADER_LEFT_MARGIN *= mScale;
422edcc752060be38bfa7e7b32691c0cc4d843622a8Ted Kremenek                DAY_HEADER_RIGHT_MARGIN *= mScale;
423c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                DAY_HEADER_BOTTOM_MARGIN *= mScale;
424c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                DAY_HEADER_ONE_DAY_LEFT_MARGIN *= mScale;
425c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                DAY_HEADER_ONE_DAY_RIGHT_MARGIN *= mScale;
426c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                DAY_HEADER_ONE_DAY_BOTTOM_MARGIN *= mScale;
427c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                DAY_HEADER_FONT_SIZE *= mScale;
4283fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner                DATE_HEADER_FONT_SIZE *= mScale;
4293fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner                CALENDAR_COLOR_SQUARE_SIZE *= mScale;
430c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                EVENT_TEXT_TOP_MARGIN *= mScale;
431c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                EVENT_TEXT_BOTTOM_MARGIN *= mScale;
432c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson                EVENT_TEXT_LEFT_MARGIN *= mScale;
433d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                EVENT_TEXT_RIGHT_MARGIN *= mScale;
434d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                EVENT_ALL_DAY_TEXT_TOP_MARGIN *= mScale;
435d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN *= mScale;
436b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                EVENT_ALL_DAY_TEXT_LEFT_MARGIN *= mScale;
4373fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner                EVENT_ALL_DAY_TEXT_RIGHT_MARGIN *= mScale;
4385f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                EVENT_RECT_TOP_MARGIN *= mScale;
43924e1e707b4c362f18e371e2bbf054a8345b57bfaChris Lattner                EVENT_RECT_BOTTOM_MARGIN *= mScale;
440d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                EVENT_RECT_LEFT_MARGIN *= mScale;
44124e1e707b4c362f18e371e2bbf054a8345b57bfaChris Lattner                EVENT_RECT_RIGHT_MARGIN *= mScale;
442d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek            }
443d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        }
444b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
445d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mResources = context.getResources();
446025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mCurrentTimeLine = mResources.getDrawable(R.drawable.timeline_week_holo_light);
447025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mTodayHeaderDrawable = mResources.getDrawable(R.drawable.today_blue_week_holo_light);
448025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mEventBoxDrawable = mResources.getDrawable(R.drawable.panel_month_event_holo_light);
449025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mEventLoader = eventLoader;
450764a7ce5217f9569e100a3445f47496ee82daf86Chris Lattner        mEventGeometry = new EventGeometry();
451025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mEventGeometry.setMinEventHeight(MIN_EVENT_HEIGHT);
452025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mEventGeometry.setHourGap(HOUR_GAP);
453d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mContext = context;
454d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mDeleteEventHelper = new DeleteEventHelper(context, null, false /* don't exit when done */);
455d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mLastPopupEventID = INVALID_EVENT_ID;
456025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mController = controller;
45751b09f2c528c8460b5465c676173324e44176d62Devang Patel        mViewSwitcher = viewSwitcher;
45851b09f2c528c8460b5465c676173324e44176d62Devang Patel        mGestureDetector = new GestureDetector(context, new CalendarGestureListener());
45951b09f2c528c8460b5465c676173324e44176d62Devang Patel        mScaleGestureDetector = new ScaleGestureDetector(getContext(), this);
46051b09f2c528c8460b5465c676173324e44176d62Devang Patel        mNumDays = numDays;
46151b09f2c528c8460b5465c676173324e44176d62Devang Patel        if (mCellHeight == 0) {
46251b09f2c528c8460b5465c676173324e44176d62Devang Patel            mCellHeight = Utils.getSharedPreference(mContext,
46351b09f2c528c8460b5465c676173324e44176d62Devang Patel                    GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT, DEFAULT_CELL_HEIGHT);
4645f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
46520dabe8b8ff7ed6d2d0158fee43755a4bc7642c3Chris Lattner
46620dabe8b8ff7ed6d2d0158fee43755a4bc7642c3Chris Lattner        init(context);
46720dabe8b8ff7ed6d2d0158fee43755a4bc7642c3Chris Lattner    }
46820dabe8b8ff7ed6d2d0158fee43755a4bc7642c3Chris Lattner
46920dabe8b8ff7ed6d2d0158fee43755a4bc7642c3Chris Lattner    private void init(Context context) {
4703fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner        setFocusable(true);
4713fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner
4723fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner        // Allow focus in touch mode so that we can do keyboard shortcuts
47391ee0140ecb60b5c1402edc9e577257636c4ca60Chris Lattner        // even after we've entered touch mode.
4743fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner        setFocusableInTouchMode(true);
4753fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner        setClickable(true);
4763fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner        setOnCreateContextMenuListener(this);
477b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
4785f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mFirstDayOfWeek = Utils.getFirstDayOfWeek(context);
4795f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
4805f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mCurrentTime = new Time(Utils.getTimeZone(context, mTZUpdater));
4815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        long currentTime = System.currentTimeMillis();
482d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mCurrentTime.set(currentTime);
483d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        //The % makes it go off at the next increment of 5 minutes.
484d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        postDelayed(mUpdateCurrentTime,
485d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek                UPDATE_CURRENT_TIME_DELAY - (currentTime % UPDATE_CURRENT_TIME_DELAY));
4869eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek        mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
487ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek
488e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        mWeek_saturdayColor = mResources.getColor(R.color.week_saturday);
4895f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mWeek_sundayColor = mResources.getColor(R.color.week_sunday);
4905f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mCalendarDateBannerTextColor = mResources.getColor(R.color.calendar_date_banner_text_color);
491c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson//        mCalendarAllDayBackground = mResources.getColor(R.color.calendar_all_day_background);
492d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mCalendarAmPmLabel = mResources.getColor(R.color.calendar_ampm_label);
4936c36be5b383875b490684bcf439d6d427298c1afChris Lattner//        mCalendarDateBannerBackground = mResources.getColor(R.color.calendar_date_banner_background);
4943fb94a4918bd427fdb12df997dd87fd1017f0388Chris Lattner//        mCalendarDateSelected = mResources.getColor(R.color.calendar_date_selected);
4955f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer//        mCalendarGridAreaBackground = mResources.getColor(R.color.calendar_grid_area_background);
496edcc752060be38bfa7e7b32691c0cc4d843622a8Ted Kremenek        mCalendarGridAreaSelected = mResources.getColor(R.color.calendar_grid_area_selected);
497d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mCalendarGridLineHorizontalColor = mResources.getColor(R.color.calendar_grid_line_horizontal_color);
498025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mCalendarGridLineVerticalColor = mResources.getColor(R.color.calendar_grid_line_vertical_color);
499025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mCalendarGridLineInnerHorizontalColor = mResources.getColor(R.color.calendar_grid_line_inner_horizontal_color);
500025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mCalendarGridLineInnerVerticalColor = mResources.getColor(R.color.calendar_grid_line_inner_vertical_color);
501025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor//        mCalendarHourBackground = mResources.getColor(R.color.calendar_hour_background);
502d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        mCalendarHourLabel = mResources.getColor(R.color.calendar_hour_label);
50351b09f2c528c8460b5465c676173324e44176d62Devang Patel//        mCalendarHourSelected = mResources.getColor(R.color.calendar_hour_selected);
504025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mSelectionColor = mResources.getColor(R.color.selection);
505025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mPressedColor = mResources.getColor(R.color.pressed);
5066c36be5b383875b490684bcf439d6d427298c1afChris Lattner        mSelectedEventTextColor = mResources.getColor(R.color.calendar_event_selected_text_color);
507025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mEventTextColor = mResources.getColor(R.color.calendar_event_text_color);
5085f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
509b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mEventTextPaint.setColor(mEventTextColor);
510b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mEventTextPaint.setTextSize(EVENT_TEXT_FONT_SIZE);
511b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mEventTextPaint.setTextAlign(Paint.Align.LEFT);
5125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mEventTextPaint.setAntiAlias(true);
5135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        int gridLineColor = mResources.getColor(R.color.calendar_grid_line_highlight_color);
5155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        Paint p = mSelectionPaint;
516d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        p.setColor(gridLineColor);
517d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        p.setStyle(Style.FILL);
518d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek        p.setAntiAlias(false);
519d97bb6c1384cb773ba5cdbd198008dec127cebadTed Kremenek
5209eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek        p = mPaint;
521ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        p.setAntiAlias(true);
522e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop
5235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Allocate space for 2 weeks worth of weekday names so that we can
5245f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // easily start the week display at any week day.
5255f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mDayStrs = new String[14];
5265f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5275f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Also create an array of 2-letter abbreviations.
528b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mDayStrs2Letter = new String[14];
5295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
530b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
531b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            int index = i - Calendar.SUNDAY;
532b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            // e.g. Tue for Tuesday
5331de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor            mDayStrs[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_MEDIUM);
5341de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor            mDayStrs[index + 7] = mDayStrs[index];
5351de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor            // e.g. Tu for Tuesday
5365f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORT);
5375f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5385f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            // If we don't have 2-letter day strings, fall back to 1-letter.
5391de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor            if (mDayStrs2Letter[index].equals(mDayStrs[index])) {
5405f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                mDayStrs2Letter[index] = DateUtils.getDayOfWeekString(i, DateUtils.LENGTH_SHORTEST);
5415f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            }
5425f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5435f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mDayStrs2Letter[index + 7] = mDayStrs2Letter[index];
5445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
545b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
546b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // Figure out how much space we need for the 3-letter abbrev names
547b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // in the worst case.
548b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        p.setTextSize(DATE_HEADER_FONT_SIZE);
5495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        p.setTypeface(mBold);
5505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        String[] dateStrs = {" 28", " 30"};
5515f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mDateStrWidth = computeMaxStringWidth(0, dateStrs, p);
5525f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        p.setTextSize(DAY_HEADER_FONT_SIZE);
5538297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mDateStrWidth += computeMaxStringWidth(0, mDayStrs, p);
5548297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
5558297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        p.setTextSize(HOURS_FONT_SIZE);
5568297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        p.setTypeface(null);
557b15132fda52046ba65eafd6f4b448f7aa16b7e8dTed Kremenek        updateIs24HourFormat();
558ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek
559e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        mAmString = DateUtils.getAMPMString(Calendar.AM);
5605f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPmString = DateUtils.getAMPMString(Calendar.PM);
5615f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        String[] ampm = {mAmString, mPmString};
5625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        p.setTextSize(AMPM_FONT_SIZE);
5635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mHoursWidth = computeMaxStringWidth(mHoursWidth, ampm, p);
5645f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mHoursWidth += HOURS_MARGIN;
5655f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5668297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        LayoutInflater inflater;
5678297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
568b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mPopupView = inflater.inflate(R.layout.bubble_event, null);
5695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPopupView.setLayoutParams(new ViewGroup.LayoutParams(
570b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                ViewGroup.LayoutParams.MATCH_PARENT,
571b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                ViewGroup.LayoutParams.WRAP_CONTENT));
5728297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mPopup = new PopupWindow(context);
5738297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mPopup.setContentView(mPopupView);
5748297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        Resources.Theme dialogTheme = getResources().newTheme();
575b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        dialogTheme.applyStyle(android.R.style.Theme_Dialog, true);
5768297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        TypedArray ta = dialogTheme.obtainStyledAttributes(new int[] {
5775f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            android.R.attr.windowBackground });
578025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mPopup.setBackgroundDrawable(ta.getDrawable(0));
579025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        ta.recycle();
580025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
5818297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // Enable touching the popup window
582025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mPopupView.setOnClickListener(this);
5838297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
584025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mBaseDate = new Time(Utils.getTimeZone(context, mTZUpdater));
5858297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        long millis = System.currentTimeMillis();
586025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        mBaseDate.set(millis);
5875f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
5888297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mEarliestStartHour = new int[mNumDays];
5898297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mHasAllDayEvent = new boolean[mNumDays];
5908297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
591b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // mLines is the array of points used with Canvas.drawLines() in
592025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        // drawGridBackground() and drawAllDayEvents().  Its size depends
593025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        // on the max number of lines that can ever be drawn by any single
594025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        // drawLines() call in either of those methods.
595b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        final int maxGridLines = (24 + 1)  // max horizontal lines we might draw
596b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                + (mNumDays + 1);  // max vertical lines we might draw
597b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mLines = new float[maxGridLines * 4];
598b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    }
599b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
600b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    /**
6015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * This is called when the popup window is pressed.
6025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
6035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public void onClick(View v) {
6045f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (v == mPopupView) {
6055f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            // Pretend it was a trackball click because that will always
6068297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            // jump to the "View event" screen.
6078297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            switchViews(true /* trackball */);
6088297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
6098297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
6104210f3dfd73ad3482f9cfa0a382e1fd78f22976dTed Kremenek
611ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek    public void updateIs24HourFormat() {
612e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        mIs24HourFormat = DateFormat.is24HourFormat(mContext);
6135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mHourStrs = mIs24HourFormat ? CalendarData.s24Hours : CalendarData.s12HoursNoAmPm;
6145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
6155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
6165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    /**
6175f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Returns the start of the selected time in milliseconds since the epoch.
6188297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     *
6198297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     * @return selected time in UTC milliseconds since the epoch.
620c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson     */
621c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    long getSelectedTimeInMillis() {
6229dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff        Time time = new Time(mBaseDate);
6235f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        time.setJulianDay(mSelectionDay);
6248297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        time.hour = mSelectionHour;
6258297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
6268297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // We ignore the "isDst" field because we want normalize() to figure
6278297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // out the correct DST value and not adjust the selected time based
6285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // on the current setting of DST.
629025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        return time.normalize(true /* ignore isDst */);
630025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    }
631025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
6328297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    Time getSelectedTime() {
6338297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        Time time = new Time(mBaseDate);
634c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        time.setJulianDay(mSelectionDay);
635c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        time.hour = mSelectionHour;
6368297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
637025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        // We ignore the "isDst" field because we want normalize() to figure
6388297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // out the correct DST value and not adjust the selected time based
639025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        // on the current setting of DST.
640c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        time.normalize(true /* ignore isDst */);
641025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor        return time;
642025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    }
643025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
644025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    /**
645c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson     * Returns the start of the selected time in minutes since midnight,
6469dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff     * local time.  The derived class must ensure that this is consistent
6479dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff     * with the return value from getSelectedTimeInMillis().
6489dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff     */
6499dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff    int getSelectedMinutesSinceMidnight() {
650c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        return mSelectionHour * MINUTES_PER_HOUR;
651c2ee10d79f70036af652a395ac1f8273f3d04e12Douglas Gregor    }
652c2ee10d79f70036af652a395ac1f8273f3d04e12Douglas Gregor
653c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson    public void setSelectedDay(Time time) {
654c1fcb7762673be706b0a40477d5e93411e918f93Anders Carlsson        mBaseDate.set(time);
6559dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff        mSelectionHour = mBaseDate.hour;
6569dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff        mSelectedEvent = null;
6579dcbfa450d751bd68fc4af8b75da381d4f6984b9Steve Naroff        mPrevSelectedEvent = null;
6585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        long millis = mBaseDate.toMillis(false /* use isDst */);
6595f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mSelectionDay = Time.getJulianDay(millis, mBaseDate.gmtoff);
6605f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mSelectedEvents.clear();
6615f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mComputeSelectedEvents = true;
6628297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
6638297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        recalc();
6648297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
6658297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // Don't draw the selection box if we are going to the "current" time
6669eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek        long currMillis = System.currentTimeMillis();
667ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        boolean recent = (currMillis - 10000) < millis && millis < currMillis;
668e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        mSelectionMode = recent ? SELECTION_HIDDEN : SELECTION_SELECTED;
6695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mRemeasure = true;
6705f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        invalidate();
6715f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
6725f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
6735f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    public Time getSelectedDay() {
6745f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        Time time = new Time(mBaseDate);
6758297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        time.setJulianDay(mSelectionDay);
6768297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        time.hour = mSelectionHour;
677b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
6785f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // We ignore the "isDst" field because we want normalize() to figure
679b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // out the correct DST value and not adjust the selected time based
6808297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // on the current setting of DST.
6818297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        time.normalize(true /* ignore isDst */);
682b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        return time;
6838297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
6845f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
685d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor    /**
686d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor     * return a negative number if "time" is comes before the visible time
687d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor     * range, a positive number if "time" is after the visible time range, and 0
6888297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     * if it is in the visible time range.
6898297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek     */
690d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor    public int compareToVisibleTimeRange(Time time) {
6918297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
6928297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        int savedHour = mBaseDate.hour;
693d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor        int savedMinute = mBaseDate.minute;
694d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor        int savedSec = mBaseDate.second;
695d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor
696d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor        mBaseDate.hour = 0;
697b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mBaseDate.minute = 0;
698b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        mBaseDate.second = 0;
699b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
700b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        if (DEBUG) {
7015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            Log.d(TAG, "Begin " + mBaseDate.toString());
7025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            Log.d(TAG, "Diff  " + time.toString());
7035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
7045f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
7058297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // Compare beginning of range
7068297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        int diff = Time.compare(time, mBaseDate);
7078297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        if (diff > 0) {
7088297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            // Compare end of range
7095572b94b76a78a35bac32d1ef04907c7a651adbaTed Kremenek            mBaseDate.monthDay += mNumDays;
710ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            mBaseDate.normalize(true);
711e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop            diff = Time.compare(time, mBaseDate);
7125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
7135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            if (DEBUG) Log.d(TAG, "End   " + mBaseDate.toString());
7145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
7155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mBaseDate.monthDay -= mNumDays;
7165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mBaseDate.normalize(true);
7178297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            if (diff < 0) {
7188297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                // in visible time
719b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                diff = 0;
7205f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            } else if (diff == 0) {
721b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                // Midnight of following day
722b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                diff = 1;
7238297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            }
7248297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
725b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
7268297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        if (DEBUG) Log.d(TAG, "Diff: " + diff);
72767d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor
72867d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        mBaseDate.hour = savedHour;
72967d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        mBaseDate.minute = savedMinute;
7305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mBaseDate.second = savedSec;
7318297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        return diff;
7328297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    }
73367d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor
7348297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek    private void recalc() {
7358297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // Set the base date to the beginning of the week if we are displaying
73667d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        // 7 days at a time.
73767d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        if (mNumDays == 7) {
73867d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor            int dayOfWeek = mBaseDate.weekDay;
73967d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor            int diff = dayOfWeek - mFirstDayOfWeek;
740b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            if (diff != 0) {
741b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                if (diff < 0) {
742b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                    diff += 7;
743b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                }
7445f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                mBaseDate.monthDay -= diff;
7455f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                mBaseDate.normalize(true /* ignore isDst */);
7465f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            }
7475f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
7488297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
7498297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        final long start = mBaseDate.toMillis(false /* use isDst */);
7508297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mFirstJulianDay = Time.getJulianDay(start, mBaseDate.gmtoff);
7518297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mLastJulianDay = mFirstJulianDay + mNumDays - 1;
752e3299ef345bb8a71901108bf83a824591eae6c1aTed Kremenek
753ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        mMonthLength = mBaseDate.getActualMaximum(Time.MONTH_DAY);
754e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        mFirstVisibleDate = mBaseDate.monthDay;
7555f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mFirstVisibleDayOfWeek = mBaseDate.weekDay;
7565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
7575f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
7585f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    @Override
7595f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    protected void onSizeChanged(int width, int height, int oldw, int oldh) {
7605f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mViewWidth = width;
7615f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mViewHeight = height;
7625f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        int gridAreaWidth = width - mHoursWidth;
7638297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mCellWidth = (gridAreaWidth - (mNumDays * DAY_GAP)) / mNumDays;
7648297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
765b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // This would be about 1 day worth in a 7 day view
7665f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mHorizontalSnapBackThreshold = width / 7;
767b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
768b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        Paint p = new Paint();
769af458c9f658335bdea482395ff4a25c75935e129Chris Lattner        p.setTextSize(HOURS_FONT_SIZE);
770af458c9f658335bdea482395ff4a25c75935e129Chris Lattner        mHoursTextHeight = (int) Math.abs(p.ascent());
771af458c9f658335bdea482395ff4a25c75935e129Chris Lattner        remeasure(width, height);
772af458c9f658335bdea482395ff4a25c75935e129Chris Lattner    }
773b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff
774af458c9f658335bdea482395ff4a25c75935e129Chris Lattner    /**
7755f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * Measures the space needed for various parts of the view after
77667d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor     * loading new events.  This can change if there are all-day events.
77767d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor     */
77867d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor    private void remeasure(int width, int height) {
7798297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
7808297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // First, clear the array of earliest start times, and the array
7818297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // indicating presence of an all-day event.
7828297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        for (int day = 0; day < mNumDays; day++) {
7838297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mEarliestStartHour[day] = 25;  // some big number
7848297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mHasAllDayEvent[day] = false;
7858297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        }
7868297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek
7878297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // Compute the layout relation between each event before measuring cell
788b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // width, as the cell width should be adjusted along with the relation.
78967d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        //
79067d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        // Examples: A (1:00pm - 1:01pm), B (1:02pm - 2:00pm)
79167d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        // We should mark them as "overwapped". Though they are not overwapped logically, but
79267d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        // minimum cell height implicitly expands the cell height of A and it should look like
79367d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        // (1:00pm - 1:15pm) after the cell height adjustment.
79467d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor
79567d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        // Compute the space needed for the all-day events, if any.
79667d8249924ef38a5375ff9c92cd21c1854d6ababDouglas Gregor        // Make a pass over all the events, and keep track of the maximum
797b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // number of all-day events in any one day.  Also, keep track of
798b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        // the earliest event in each day.
799b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        int maxAllDayEvents = 0;
8005f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final ArrayList<Event> events = mEvents;
8015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final int len = events.size();
8025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Num of all-day-events on each day.
8035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final int eventsCount[] = new int[mLastJulianDay - mFirstJulianDay + 1];
8048297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        Arrays.fill(eventsCount, 0);
8058297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        for (int ii = 0; ii < len; ii++) {
8068297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            Event event = events.get(ii);
8078297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            if (event.startDay > mLastJulianDay || event.endDay < mFirstJulianDay) {
80807ba046c7d7bbc1f42b86b3ed012eec8fc7849daTed Kremenek                continue;
809ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            }
810e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop            if (event.allDay) {
8115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                final int firstDay = Math.max(event.startDay, mFirstJulianDay);
8120196cab54007ff072ec2642da8911c6b7e8d3fb5Fariborz Jahanian                final int lastDay = Math.min(event.endDay, mLastJulianDay);
8135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                for (int day = firstDay; day <= lastDay; day++) {
8145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    final int count = ++eventsCount[day - mFirstJulianDay];
8155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    if (maxAllDayEvents < count) {
8165f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                        maxAllDayEvents = count;
817507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff                    }
81861f62165220e75694fe333179c78815e2e48d71fTed Kremenek                }
8195f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
82061f62165220e75694fe333179c78815e2e48d71fTed Kremenek                int daynum = event.startDay - mFirstJulianDay;
82161f62165220e75694fe333179c78815e2e48d71fTed Kremenek                int durationDays = event.endDay - event.startDay + 1;
8225f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                if (daynum < 0) {
8231de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                    durationDays += daynum;
8241de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                    daynum = 0;
8251de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                }
8265f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                if (daynum + durationDays > mNumDays) {
8271de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                    durationDays = mNumDays - daynum;
8281de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                }
8291de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                for (int day = daynum; durationDays > 0; day++, durationDays--) {
8301de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                    mHasAllDayEvent[day] = true;
8311de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor                }
8321de05feeeafe5b215fe7617594a7076a5192a6e2Douglas Gregor            } else {
833b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff                int daynum = event.startDay - mFirstJulianDay;
834507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff                int hour = event.startTime / 60;
83561f62165220e75694fe333179c78815e2e48d71fTed Kremenek                if (daynum >= 0 && hour < mEarliestStartHour[daynum]) {
836507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff                    mEarliestStartHour[daynum] = hour;
8375f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                }
8385f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
8395f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                // Also check the end hour in case the event spans more than
8405f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                // one day.
8418297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                daynum = event.endDay - mFirstJulianDay;
8428297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                hour = event.endTime / 60;
8438297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                if (daynum < mNumDays && hour < mEarliestStartHour[daynum]) {
8448297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                    mEarliestStartHour[daynum] = hour;
8453f0767b74b16b37bcce60ff457609161a0665784Ted Kremenek                }
846ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek            }
847e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        }
8485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mMaxAllDayEvents = maxAllDayEvents;
8495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
8505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // Calculate mAllDayHeight
8515f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mFirstCell = DAY_HEADER_HEIGHT;
8525f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        int allDayHeight = 0;
8531060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek        if (maxAllDayEvents > 0) {
854225a2d946f73aed3b38f265fcb4e7d7d88136928Ted Kremenek            // If there is at most one all-day event per day, then use less
8557d5c2f241c74e5f8d9ec492e8f9f268e5e9ae41fDouglas Gregor            // space (but more than the space for a single event).
8565f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            if (maxAllDayEvents == 1) {
8571060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek                allDayHeight = SINGLE_ALLDAY_HEIGHT;
8581060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek            } else {
8597d5c2f241c74e5f8d9ec492e8f9f268e5e9ae41fDouglas Gregor                // Allow the all-day area to grow in height depending on the
8607d5c2f241c74e5f8d9ec492e8f9f268e5e9ae41fDouglas Gregor                // number of all-day events we need to show, up to a limit.
8617d5c2f241c74e5f8d9ec492e8f9f268e5e9ae41fDouglas Gregor                allDayHeight = maxAllDayEvents * MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
8627d5c2f241c74e5f8d9ec492e8f9f268e5e9ae41fDouglas Gregor                if (allDayHeight > MAX_ALLDAY_HEIGHT) {
8635f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    allDayHeight = MAX_ALLDAY_HEIGHT;
8641060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek                }
8651060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek            }
8667d5c2f241c74e5f8d9ec492e8f9f268e5e9ae41fDouglas Gregor            mFirstCell = DAY_HEADER_HEIGHT + allDayHeight + ALLDAY_TOP_MARGIN;
867b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff        } else {
868b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            mSelectionAllDay = false;
8695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
8705f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mAllDayHeight = allDayHeight;
8715f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
8725f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mGridAreaHeight = height - mFirstCell;
8735f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
8748297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // The min is where 24 hours cover the entire visible area
8758297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        mMinCellHeight = (height - DAY_HEADER_HEIGHT) / 24 - HOUR_GAP;
8768297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        if (mCellHeight < mMinCellHeight) {
8778297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek            mCellHeight = mMinCellHeight;
878225a2d946f73aed3b38f265fcb4e7d7d88136928Ted Kremenek        }
879ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek
880e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        mNumHours = mGridAreaHeight / mCellHeight;
8815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mEventGeometry.setHourHeight(mCellHeight);
8825f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
8835f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final long minimumDurationMillis = (long)
8845f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                (MIN_EVENT_HEIGHT * DateUtils.MINUTE_IN_MILLIS / (mCellHeight / 60.0f));
8855f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        Event.computePositions(events, minimumDurationMillis);
8865f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
887507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        // Compute the top of our reachable view
8885f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight;
889507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        if (DEBUG) {
890b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            Log.e(TAG, "mViewStartY: " + mViewStartY);
891d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor            Log.e(TAG, "mMaxViewStartY: " + mMaxViewStartY);
892d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor        }
893d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor        if (mViewStartY > mMaxViewStartY) {
894d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor            mViewStartY = mMaxViewStartY;
895d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor            computeFirstHour();
896d921cf976b4769af8d06d6763a2547dadf7940abDouglas Gregor        }
897507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff
898507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        if (mFirstHour == -1) {
899507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff            initFirstHour();
9005f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mFirstHourOffset = 0;
9015f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        }
9025f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
9035f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        // When we change the base date, the number of all-day events may
9048297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // change and that changes the cell height.  When we switch dates,
9058297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // we use the mFirstHourOffset from the previous view, but that may
9068297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        // be too large for the new view if the cell height is smaller.
9078297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        if (mFirstHourOffset >= mCellHeight + HOUR_GAP) {
90896f22423c52c22d263b4e5f7ee762122b607459eTed Kremenek            mFirstHourOffset = mCellHeight + HOUR_GAP - 1;
909ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        }
910e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        mViewStartY = mFirstHour * (mCellHeight + HOUR_GAP) - mFirstHourOffset;
9115f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
9125f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        final int eventAreaWidth = mNumDays * (mCellWidth + DAY_GAP);
9135f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        //When we get new events we don't want to dismiss the popup unless the event changes
9145f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (mSelectedEvent != null && mLastPopupEventID != mSelectedEvent.id) {
9155f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            mPopup.dismiss();
916507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        }
9175f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        mPopup.setWidth(eventAreaWidth - 20);
918507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff        mPopup.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
919b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff    }
920025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor
921025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor    /**
922025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor     * Initialize the state for another view.  The given view is one that has
923025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor     * its own bitmap and will use an animation to replace the current view.
924025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor     * The current view and new view are either both Week views or both Day
925025452fa0eda63e150cfaeebe64f0a19c96b3a06Douglas Gregor     * views.  They differ in their base date.
926507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff     *
927b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff     * @param view the view to initialize.
9285f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     */
9295f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    private void initView(DayView view) {
9305f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        view.mSelectionHour = mSelectionHour;
9315f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        view.mSelectedEvents.clear();
9328297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        view.mComputeSelectedEvents = true;
9338297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        view.mFirstHour = mFirstHour;
9348297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        view.mFirstHourOffset = mFirstHourOffset;
9358297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek        view.remeasure(getWidth(), getHeight());
9369eea2ca5f2cb5d77569274702b5b06273e426dc2Ted Kremenek
937ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek        view.mSelectedEvent = null;
938e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        view.mPrevSelectedEvent = null;
9395f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        view.mFirstDayOfWeek = mFirstDayOfWeek;
9405f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer        if (view.mEvents.size() > 0) {
9415f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            view.mSelectionAllDay = mSelectionAllDay;
942c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner        } else {
943c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner            view.mSelectionAllDay = false;
944c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner        }
945c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner
946c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner        // Redraw the screen so that the selection box will be redrawn.  We may
947c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner        // have scrolled to a different part of the day in some other view
948c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner        // so the selection box in this view may no longer be visible.
949c8edf6bd5f76bf50411d037c83d36847edfa68f0Chris Lattner        view.recalc();
9505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer    }
9515f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer
9521060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek    /**
953507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff     * Switch to another view based on what was selected (an event or a free
9545f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer     * slot) and how it was selected (by touch or by trackball).
955507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff     *
9561060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek     * @param trackBallSelection true if the selection was made using the
9570de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor     * trackball.
9580de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor     */
9590de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor    private void switchViews(boolean trackBallSelection) {
9600de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor        Event selectedEvent = mSelectedEvent;
9611060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek
9621060aff23f72135f8b50034a1e80f16725ebc56cTed Kremenek        mPopup.dismiss();
9630de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor        mLastPopupEventID = INVALID_EVENT_ID;
9640de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor        if (mNumDays > 1) {
9650de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor            // This is the Week view.
9660de9d8857b715c2f45c987651f4ce06d73330d93Douglas Gregor            // With touch, we always switch to Day/Agenda View
967b5a69586f1b8855ee4c1f0bb7a8f0ff4fe32ce09Steve Naroff            // With track ball, if we selected a free slot, then create an event.
968507f2d5811bd7da1a4d9d2f4960f32177dfab9deSteve Naroff            // If we selected a specific event, switch to EventInfo view.
9695f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            if (trackBallSelection) {
9705f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                if (selectedEvent == null) {
9715f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    // Switch to the EditEvent view
9725f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    long startMillis = getSelectedTimeInMillis();
9735f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
9748297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                    mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1,
9758297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                            startMillis, endMillis, 0, 0);
9768297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                } else {
9778297777fbe19c4d39e8a70c55346474868055fa1Ted Kremenek                    // Switch to the EventInfo view
9782dc9ac73a8a34cabf261a81a1653d7379065ac61Ted Kremenek                    mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id,
979ec0aa78745f7b3bc96c20fffd1115bf26aaa0eadTed Kremenek                            selectedEvent.startMillis, selectedEvent.endMillis, 0, 0);
980e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop                }
9815f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            } else {
9825f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                // This was a touch selection.  If the touch selected a single
983fe795956194141c91ae555985c9b930595bff43fChris Lattner                // unambiguous event, then view that event.  Otherwise go to
984fe795956194141c91ae555985c9b930595bff43fChris Lattner                // Day/Agenda view.
985fe795956194141c91ae555985c9b930595bff43fChris Lattner                if (mSelectedEvents.size() == 1) {
986fe795956194141c91ae555985c9b930595bff43fChris Lattner                    mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id,
9876a0ef4b83c91a6d6d5acb4ed5577c4659fe022a3Anders Carlsson                            selectedEvent.startMillis, selectedEvent.endMillis, 0, 0);
988b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson                }
989dfab34a696d1dba8622248c31aaf605906cb6109Anders Carlsson            }
99039c47b56f45437bbc49c9568b7308a400234a730Anders Carlsson        } else {
99139c47b56f45437bbc49c9568b7308a400234a730Anders Carlsson            // This is the Day view.
992b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson            // If we selected a free slot, then create an event.
993b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson            // If we selected an event, then go to the EventInfo view.
994b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson            if (selectedEvent == null) {
995b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson                // Switch to the EditEvent view
996b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson                long startMillis = getSelectedTimeInMillis();
997ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek                long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
998b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson
999b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson                mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1, startMillis,
1000fe795956194141c91ae555985c9b930595bff43fChris Lattner                        endMillis, 0, 0);
1001dfab34a696d1dba8622248c31aaf605906cb6109Anders Carlsson            } else {
1002dfab34a696d1dba8622248c31aaf605906cb6109Anders Carlsson                mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, selectedEvent.id,
1003dfab34a696d1dba8622248c31aaf605906cb6109Anders Carlsson                        selectedEvent.startMillis, selectedEvent.endMillis, 0, 0);
1004db6ed1786bf460e1143f67d14bf2d71ad9856f81Chris Lattner            }
1005db6ed1786bf460e1143f67d14bf2d71ad9856f81Chris Lattner        }
1006b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson    }
1007cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor
1008cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor    @Override
1009cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor    public boolean onKeyUp(int keyCode, KeyEvent event) {
1010cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor        mScrolling = false;
1011cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor        long duration = event.getEventTime() - event.getDownTime();
1012cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor
1013cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor        switch (keyCode) {
1014cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor            case KeyEvent.KEYCODE_DPAD_CENTER:
101539c47b56f45437bbc49c9568b7308a400234a730Anders Carlsson                if (mSelectionMode == SELECTION_HIDDEN) {
1016cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                    // Don't do anything unless the selection is visible.
1017dfab34a696d1dba8622248c31aaf605906cb6109Anders Carlsson                    break;
1018cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                }
1019dfab34a696d1dba8622248c31aaf605906cb6109Anders Carlsson
1020458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                if (mSelectionMode == SELECTION_PRESSED) {
1021458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    // This was the first press when there was nothing selected.
1022458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    // Change the selection from the "pressed" state to the
1023458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    // the "selected" state.  We treat short-press and
1024cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                    // long-press the same here because nothing was selected.
1025cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                    mSelectionMode = SELECTION_SELECTED;
1026458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    invalidate();
1027458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    break;
1028458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                }
1029458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner
1030458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                // Check the duration to determine if this was a short press
1031458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                if (duration < ViewConfiguration.getLongPressTimeout()) {
1032458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    switchViews(true /* trackball */);
1033458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                } else {
1034458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    mSelectionMode = SELECTION_LONGPRESS;
1035458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    invalidate();
1036458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    performLongClick();
1037458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                }
1038458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                break;
1039458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner//            case KeyEvent.KEYCODE_BACK:
1040458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner//                if (event.isTracking() && !event.isCanceled()) {
1041458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner//                    mPopup.dismiss();
1042458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner//                    mContext.finish();
1043458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner//                    return true;
1044458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner//                }
1045458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner//                break;
1046458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner        }
1047458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner        return super.onKeyUp(keyCode, event);
1048458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner    }
1049458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner
1050458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner    @Override
1051458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner    public boolean onKeyDown(int keyCode, KeyEvent event) {
1052458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner        if (mSelectionMode == SELECTION_HIDDEN) {
1053458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
1054458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    || keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_UP
1055458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                    || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
1056458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                // Display the selection box but don't move or select it
1057458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                // on this key press.
1058458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                mSelectionMode = SELECTION_SELECTED;
1059458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                invalidate();
1060458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                return true;
1061458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner            } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
1062458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                // Display the selection box but don't select it
1063458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                // on this key press.
1064458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                mSelectionMode = SELECTION_PRESSED;
1065458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                invalidate();
1066458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner                return true;
1067458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner            }
1068458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner        }
1069458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner
1070458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner        mSelectionMode = SELECTION_SELECTED;
1071458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner        mScrolling = false;
1072fb5058ef67c054296c88db18ab1b3717845cb71dChris Lattner        boolean redraw;
1073fb5058ef67c054296c88db18ab1b3717845cb71dChris Lattner        int selectionDay = mSelectionDay;
1074458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner
1075458cd9c8a79b25b87dcea43c9d97a4c59f194799Chris Lattner        switch (keyCode) {
1076b327793860737d1c103a73aeda8057dd628a101dChris Lattner        case KeyEvent.KEYCODE_DEL:
1077b327793860737d1c103a73aeda8057dd628a101dChris Lattner            // Delete the selected event, if any
1078b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson            Event selectedEvent = mSelectedEvent;
1079ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            if (selectedEvent == null) {
1080ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek                return false;
1081ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            }
1082ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            mPopup.dismiss();
1083ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            mLastPopupEventID = INVALID_EVENT_ID;
1084b327793860737d1c103a73aeda8057dd628a101dChris Lattner
1085b327793860737d1c103a73aeda8057dd628a101dChris Lattner            long begin = selectedEvent.startMillis;
1086b327793860737d1c103a73aeda8057dd628a101dChris Lattner            long end = selectedEvent.endMillis;
1087b327793860737d1c103a73aeda8057dd628a101dChris Lattner            long id = selectedEvent.id;
1088b327793860737d1c103a73aeda8057dd628a101dChris Lattner            mDeleteEventHelper.delete(begin, end, id, -1);
1089b327793860737d1c103a73aeda8057dd628a101dChris Lattner            return true;
1090ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        case KeyEvent.KEYCODE_ENTER:
1091ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            switchViews(true /* trackball or keyboard */);
1092b327793860737d1c103a73aeda8057dd628a101dChris Lattner            return true;
1093b327793860737d1c103a73aeda8057dd628a101dChris Lattner        case KeyEvent.KEYCODE_BACK:
1094b327793860737d1c103a73aeda8057dd628a101dChris Lattner            if (event.getRepeatCount() == 0) {
1095b327793860737d1c103a73aeda8057dd628a101dChris Lattner                event.startTracking();
1096b327793860737d1c103a73aeda8057dd628a101dChris Lattner                return true;
1097ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            }
1098b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson            return super.onKeyDown(keyCode, event);
1099b327793860737d1c103a73aeda8057dd628a101dChris Lattner        case KeyEvent.KEYCODE_DPAD_LEFT:
1100b327793860737d1c103a73aeda8057dd628a101dChris Lattner            if (mSelectedEvent != null) {
1101b327793860737d1c103a73aeda8057dd628a101dChris Lattner                mSelectedEvent = mSelectedEvent.nextLeft;
1102b327793860737d1c103a73aeda8057dd628a101dChris Lattner            }
1103b327793860737d1c103a73aeda8057dd628a101dChris Lattner            if (mSelectedEvent == null) {
1104b327793860737d1c103a73aeda8057dd628a101dChris Lattner                mLastPopupEventID = INVALID_EVENT_ID;
1105b327793860737d1c103a73aeda8057dd628a101dChris Lattner                selectionDay -= 1;
1106b327793860737d1c103a73aeda8057dd628a101dChris Lattner            }
1107b327793860737d1c103a73aeda8057dd628a101dChris Lattner            redraw = true;
1108b327793860737d1c103a73aeda8057dd628a101dChris Lattner            break;
1109b327793860737d1c103a73aeda8057dd628a101dChris Lattner
111085759278332404e96d4bb89d0e976e46158cd026Chris Lattner        case KeyEvent.KEYCODE_DPAD_RIGHT:
111185759278332404e96d4bb89d0e976e46158cd026Chris Lattner            if (mSelectedEvent != null) {
111285759278332404e96d4bb89d0e976e46158cd026Chris Lattner                mSelectedEvent = mSelectedEvent.nextRight;
111385759278332404e96d4bb89d0e976e46158cd026Chris Lattner            }
1114b327793860737d1c103a73aeda8057dd628a101dChris Lattner            if (mSelectedEvent == null) {
1115b327793860737d1c103a73aeda8057dd628a101dChris Lattner                mLastPopupEventID = INVALID_EVENT_ID;
1116b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson                selectionDay += 1;
1117ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            }
1118ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            redraw = true;
1119ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            break;
1120ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
1121b327793860737d1c103a73aeda8057dd628a101dChris Lattner        case KeyEvent.KEYCODE_DPAD_UP:
1122b327793860737d1c103a73aeda8057dd628a101dChris Lattner            if (mSelectedEvent != null) {
1123b327793860737d1c103a73aeda8057dd628a101dChris Lattner                mSelectedEvent = mSelectedEvent.nextUp;
1124b327793860737d1c103a73aeda8057dd628a101dChris Lattner            }
1125b327793860737d1c103a73aeda8057dd628a101dChris Lattner            if (mSelectedEvent == null) {
1126b327793860737d1c103a73aeda8057dd628a101dChris Lattner                mLastPopupEventID = INVALID_EVENT_ID;
1127ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek                if (!mSelectionAllDay) {
1128ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek                    mSelectionHour -= 1;
1129b327793860737d1c103a73aeda8057dd628a101dChris Lattner                    adjustHourSelection();
1130ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek                    mSelectedEvents.clear();
1131ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek                    mComputeSelectedEvents = true;
1132b327793860737d1c103a73aeda8057dd628a101dChris Lattner                }
1133b327793860737d1c103a73aeda8057dd628a101dChris Lattner            }
1134ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            redraw = true;
1135b327793860737d1c103a73aeda8057dd628a101dChris Lattner            break;
1136b327793860737d1c103a73aeda8057dd628a101dChris Lattner
1137b327793860737d1c103a73aeda8057dd628a101dChris Lattner        case KeyEvent.KEYCODE_DPAD_DOWN:
1138b327793860737d1c103a73aeda8057dd628a101dChris Lattner            if (mSelectedEvent != null) {
1139cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                mSelectedEvent = mSelectedEvent.nextDown;
1140cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor            }
1141cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor            if (mSelectedEvent == null) {
1142cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                mLastPopupEventID = INVALID_EVENT_ID;
1143cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                if (mSelectionAllDay) {
1144cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                    mSelectionAllDay = false;
1145cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor                } else {
114610ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner                    mSelectionHour++;
114710ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner                    adjustHourSelection();
114810ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner                    mSelectedEvents.clear();
114910ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner                    mComputeSelectedEvents = true;
115010ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner                }
115110ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner            }
115210ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner            redraw = true;
115310ca96ae9aed6906c3302403ef1a146a8d4c6b74Chris Lattner            break;
1154d04c6e23f2e10eeb9936778d67f4a1c4a14cc4f6Anders Carlsson
1155b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson        default:
1156b235fc2cf37621c7fc6511bb2b8788c95f9fb9fcAnders Carlsson            return super.onKeyDown(keyCode, event);
1157d04c6e23f2e10eeb9936778d67f4a1c4a14cc4f6Anders Carlsson        }
1158cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor
1159cd7d5a9dc558178ed7a66032f888781b3c592e4fDouglas Gregor        if ((selectionDay < mFirstJulianDay) || (selectionDay > mLastJulianDay)) {
1160fe795956194141c91ae555985c9b930595bff43fChris Lattner            DayView view = (DayView) mViewSwitcher.getNextView();
1161fe795956194141c91ae555985c9b930595bff43fChris Lattner            Time date = view.mBaseDate;
1162fe795956194141c91ae555985c9b930595bff43fChris Lattner            date.set(mBaseDate);
1163fe795956194141c91ae555985c9b930595bff43fChris Lattner            if (selectionDay < mFirstJulianDay) {
1164fe795956194141c91ae555985c9b930595bff43fChris Lattner                date.monthDay -= mNumDays;
1165fe795956194141c91ae555985c9b930595bff43fChris Lattner            } else {
1166fe795956194141c91ae555985c9b930595bff43fChris Lattner                date.monthDay += mNumDays;
1167ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek            }
1168ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek            date.normalize(true /* ignore isDst */);
1169ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            view.mSelectionDay = selectionDay;
1170ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
1171ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek            initView(view);
1172ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
1173ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            Time end = new Time(date);
1174ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            end.monthDay += mNumDays - 1;
1175ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek            Log.d(TAG, "onKeyDown");
1176ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            mController.sendEvent(this, EventType.GO_TO, date, end, -1, ViewType.CURRENT);
1177ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            return true;
1178ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        }
1179ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        mSelectionDay = selectionDay;
1180ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        mSelectedEvents.clear();
1181ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        mComputeSelectedEvents = true;
1182ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
1183ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        if (redraw) {
1184ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            invalidate();
1185ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek            return true;
1186ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek        }
1187ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek
1188ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek        return super.onKeyDown(keyCode, event);
1189ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek    }
1190ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek
1191ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek    private View switchViews(boolean forward, float xOffSet, float width) {
1192ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek        if (DEBUG) Log.d(TAG, "switchViews(" + forward + ")...");
1193ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        float progress = Math.abs(xOffSet) / width;
1194ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek        if (progress > 1.0f) {
1195ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek            progress = 1.0f;
1196ce2fc3a343ea6098a96d587071cee7299f11957aTed Kremenek        }
1197ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek
119845b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson        float inFromXValue, inToXValue;
119945b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson        float outFromXValue, outToXValue;
120045b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson        if (forward) {
120145b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson            inFromXValue = 1.0f - progress;
120245b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson            inToXValue = 0.0f;
120345b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson            outFromXValue = -progress;
120445b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson            outToXValue = -1.0f;
120545b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson        } else {
120645b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson            inFromXValue = progress - 1.0f;
120745b050e72d058131e6f169fe54888bb91a003fb5Anders Carlsson            inToXValue = 0.0f;
1208ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek            outFromXValue = progress;
1209ec2f7dccb1f30ae137f74e764e44c2332b0a2ec0Ted Kremenek            outToXValue = 1.0f;
1210fe795956194141c91ae555985c9b930595bff43fChris Lattner        }
1211fe795956194141c91ae555985c9b930595bff43fChris Lattner
12121f85acd5ff54345788b93111197088160fe9f06eTed Kremenek        // We have to allocate these animation objects each time we switch views
12131f85acd5ff54345788b93111197088160fe9f06eTed Kremenek        // because that is the only way to set the animation parameters.
1214e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        TranslateAnimation inAnimation = new TranslateAnimation(
1215fe795956194141c91ae555985c9b930595bff43fChris Lattner                Animation.RELATIVE_TO_SELF, inFromXValue,
121678492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                Animation.RELATIVE_TO_SELF, inToXValue,
121778492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                Animation.ABSOLUTE, 0.0f,
121878492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                Animation.ABSOLUTE, 0.0f);
121978492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek
122078492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        TranslateAnimation outAnimation = new TranslateAnimation(
122178492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                Animation.RELATIVE_TO_SELF, outFromXValue,
122278492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                Animation.RELATIVE_TO_SELF, outToXValue,
122378492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                Animation.ABSOLUTE, 0.0f,
122478492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                Animation.ABSOLUTE, 0.0f);
122578492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek
122678492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        // Reduce the animation duration based on how far we have already swiped.
122778492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        long duration = (long) (ANIMATION_DURATION * (1.0f - progress));
122878492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        inAnimation.setDuration(duration);
122978492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        outAnimation.setDuration(duration);
123078492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        mViewSwitcher.setInAnimation(inAnimation);
123178492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        mViewSwitcher.setOutAnimation(outAnimation);
123278492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek
123378492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        DayView view = (DayView) mViewSwitcher.getCurrentView();
123478492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        view.cleanup();
123578492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        mViewSwitcher.showNext();
123678492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        view = (DayView) mViewSwitcher.getCurrentView();
123778492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        view.requestFocus();
123878492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        view.reloadEvents();
123978492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek
124078492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        // Update the mini-month (but defer it until the animation
124178492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        // completes, to avoid stutter.)
124278492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        final Time start = new Time(view.mBaseDate);
124378492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        final Time end = new Time(view.mBaseDate);
124478492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        end.monthDay += mNumDays;
124578492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        end.normalize(true);
124678492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        postDelayed(new Runnable() {
124778492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                public void run() {
124878492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                    mController.sendEvent(this, EventType.GO_TO,
124978492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                                          start, end, -1, ViewType.CURRENT);
125078492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek                }
125178492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek            }, duration);
125278492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek
125378492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek        return view;
125478492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek    }
125578492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek
1256e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop    // This is called after scrolling stops to move the selected hour
125778492dae00fb85e0da0f966df4745edafdafb66cTed Kremenek    // to the visible part of the screen.
1258b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    private void resetSelectedHour() {
1259a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        if (mSelectionHour < mFirstHour + 1) {
1260a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            mSelectionHour = mFirstHour + 1;
1261b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            mSelectedEvent = null;
12627ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff            mSelectedEvents.clear();
12637ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff            mComputeSelectedEvents = true;
12643b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian        } else if (mSelectionHour > mFirstHour + mNumHours - 3) {
1265b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            mSelectionHour = mFirstHour + mNumHours - 3;
1266af526779b36f43f59e47cfc5b10bb0c48562f4a9Ted Kremenek            mSelectedEvent = null;
1267af526779b36f43f59e47cfc5b10bb0c48562f4a9Ted Kremenek            mSelectedEvents.clear();
1268a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            mComputeSelectedEvents = true;
1269a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        }
1270af526779b36f43f59e47cfc5b10bb0c48562f4a9Ted Kremenek    }
1271b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
1272a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek    private void initFirstHour() {
12737ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff        mFirstHour = mSelectionHour - mNumHours / 5;
127493b2bdb866d49e3dab4623a822db281042e87382Daniel Dunbar        if (mFirstHour < 0) {
1275b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            mFirstHour = 0;
1276606efdfeb9eef70871eb62a26dfd115e209e899cFariborz Jahanian        } else if (mFirstHour + mNumHours > 24) {
1277606efdfeb9eef70871eb62a26dfd115e209e899cFariborz Jahanian            mFirstHour = 24 - mNumHours;
1278ff981021554a4965943ca2d19019cea811614602Ted Kremenek        }
1279ff981021554a4965943ca2d19019cea811614602Ted Kremenek    }
1280ff981021554a4965943ca2d19019cea811614602Ted Kremenek
1281ff981021554a4965943ca2d19019cea811614602Ted Kremenek    /**
1282ff981021554a4965943ca2d19019cea811614602Ted Kremenek     * Recomputes the first full hour that is visible on screen after the
1283ff981021554a4965943ca2d19019cea811614602Ted Kremenek     * screen is scrolled.
1284ff981021554a4965943ca2d19019cea811614602Ted Kremenek     */
1285ff981021554a4965943ca2d19019cea811614602Ted Kremenek    private void computeFirstHour() {
12867ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff        // Compute the first full hour that is visible on screen
12877ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff        mFirstHour = (mViewStartY + mCellHeight + HOUR_GAP - 1) / (mCellHeight + HOUR_GAP);
12887ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff        mFirstHourOffset = mFirstHour * (mCellHeight + HOUR_GAP) - mViewStartY;
12897ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff    }
12907ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff
129193b2bdb866d49e3dab4623a822db281042e87382Daniel Dunbar    private void adjustHourSelection() {
12927794cb85d394750db0631c02b7aa7837ae56764cFariborz Jahanian        if (mSelectionHour < 0) {
12937573098b83e780d1c5bea13b384b610d8f155676Steve Naroff            mSelectionHour = 0;
12947573098b83e780d1c5bea13b384b610d8f155676Steve Naroff            if (mMaxAllDayEvents > 0) {
1295b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                mPrevSelectedEvent = null;
12963b1191d7eaf2f4984564e01ab84b6713a9d80e70Fariborz Jahanian                mSelectionAllDay = true;
1297b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            }
1298be4b3332c2d17c03af6d015d9e8e2cc524806e6eSteve Naroff        }
12997ba138abd329e591a8f6d5001f60dd7082f71b3bSteve Naroff
1300be4b3332c2d17c03af6d015d9e8e2cc524806e6eSteve Naroff        if (mSelectionHour > 23) {
1301b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            mSelectionHour = 23;
1302a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        }
1303b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
1304a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        // If the selected hour is at least 2 time slots from the top and
1305b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        // bottom of the screen, then don't scroll the view.
1306b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        if (mSelectionHour < mFirstHour + 1) {
1307b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            // If there are all-days events for the selected day but there
1308af526779b36f43f59e47cfc5b10bb0c48562f4a9Ted Kremenek            // are no more normal events earlier in the day, then jump to
1309af526779b36f43f59e47cfc5b10bb0c48562f4a9Ted Kremenek            // the all-day event area.
1310e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop            // Exception 1: allow the user to scroll to 8am with the trackball
1311b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            // before jumping to the all-day event area.
1312b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian            // Exception 2: if 12am is on screen, then allow the user to select
1313a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            // 12am before going up to the all-day event area.
1314a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            int daynum = mSelectionDay - mFirstJulianDay;
131504be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek            if (mMaxAllDayEvents > 0 && mEarliestStartHour[daynum] > mSelectionHour
131604be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                    && mFirstHour > 0 && mFirstHour < 8) {
131704be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                mPrevSelectedEvent = null;
1318a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek                mSelectionAllDay = true;
1319a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek                mSelectionHour = mFirstHour + 1;
132004be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                return;
13217794cb85d394750db0631c02b7aa7837ae56764cFariborz Jahanian            }
132204be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek
132304be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek            if (mFirstHour > 0) {
132404be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                mFirstHour -= 1;
132504be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                mViewStartY -= (mCellHeight + HOUR_GAP);
132604be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                if (mViewStartY < 0) {
132704be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                    mViewStartY = 0;
132804be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                }
132904be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                return;
1330a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            }
133104be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek        }
1332a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek
133304be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek        if (mSelectionHour > mFirstHour + mNumHours - 3) {
133404be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek            if (mFirstHour < 24 - mNumHours) {
133504be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                mFirstHour += 1;
133604be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                mViewStartY += (mCellHeight + HOUR_GAP);
133704be5aae2dc17292e2007401599638f3c6ba30deTed Kremenek                if (mViewStartY > mMaxViewStartY) {
1338e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop                    mViewStartY = mMaxViewStartY;
1339b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                }
1340b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                return;
1341a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            } else if (mFirstHour == 24 - mNumHours && mFirstHourOffset > 0) {
1342b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian                mViewStartY = mMaxViewStartY;
1343a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            }
1344b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        }
134589079eac7d6e3091b74b477a4a5ff0cdaf35496cFariborz Jahanian    }
134689079eac7d6e3091b74b477a4a5ff0cdaf35496cFariborz Jahanian
1347b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    void clearCachedEvents() {
13489c1efff14b0e001f73c875ed5b184fb3569d641dTed Kremenek        mLastReloadMillis = 0;
1349b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    }
1350a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek
1351bd49a647afd9cc534fef13cadf652d4e9c396e2bFariborz Jahanian    private Runnable mCancelCallback = new Runnable() {
1352bd49a647afd9cc534fef13cadf652d4e9c396e2bFariborz Jahanian        public void run() {
1353a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            clearCachedEvents();
1354b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        }
1355b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian    };
1356b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
1357ccdbc5c6f14965d91a352d114c109a9d4d9552b8Steve Naroff    /* package */ void reloadEvents() {
1358b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        // Protect against this being called before this view has been
1359b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        // initialized.
13608aab17e82aab6b3f266ef1884253226d2cb8b8d8Fariborz Jahanian//        if (mContext == null) {
13618aab17e82aab6b3f266ef1884253226d2cb8b8d8Fariborz Jahanian//            return;
1362a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek//        }
1363a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek
13647573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        // Make sure our time zones are up to date
1365a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        mTZUpdater.run();
1366a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek
13677573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        mSelectedEvent = null;
1368a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        mPrevSelectedEvent = null;
1369a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        mSelectedEvents.clear();
13707573098b83e780d1c5bea13b384b610d8f155676Steve Naroff
1371a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        // The start date is the beginning of the week at 12am
1372a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        Time weekStart = new Time(Utils.getTimeZone(mContext, mTZUpdater));
13737573098b83e780d1c5bea13b384b610d8f155676Steve Naroff        weekStart.set(mBaseDate);
1374b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        weekStart.hour = 0;
1375b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        weekStart.minute = 0;
1376b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        weekStart.second = 0;
1377b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        long millis = weekStart.normalize(true /* ignore isDst */);
1378b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
1379a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        // Avoid reloading events unnecessarily.
1380b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        if (millis == mLastReloadMillis) {
1381a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            return;
1382b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        }
1383b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        mLastReloadMillis = millis;
1384b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian
13859c1efff14b0e001f73c875ed5b184fb3569d641dTed Kremenek        // load events in the background
13869c1efff14b0e001f73c875ed5b184fb3569d641dTed Kremenek//        mContext.startProgressSpinner();
1387e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        final ArrayList<Event> events = new ArrayList<Event>();
1388b210bd0404f84b99259c9987d347a44d3c202238Fariborz Jahanian        mEventLoader.loadEventsInBackground(mNumDays, events, millis, new Runnable() {
1389fe795956194141c91ae555985c9b930595bff43fChris Lattner            public void run() {
1390c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian                mEvents = events;
139178a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian                if (mAllDayEvents == null) {
1392bd144b7ac70cc96cb1ff822347c2bb9b330729ecFariborz Jahanian                    mAllDayEvents = new ArrayList<Event>();
139378a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian                } else {
1394c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian                    mAllDayEvents.clear();
1395c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian                }
1396c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian
1397a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                // Create a shorter array for all day events
1398a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                for(Event e : events) {
1399c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian                    if (e.allDay) {
1400c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian                        mAllDayEvents.add(e);
1401c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian                    }
1402a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                }
1403c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian
1404a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                // New events, new layouts
1405a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                if (mLayouts == null || mLayouts.length < events.size()) {
1406a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                    mLayouts = new StaticLayout[events.size()];
1407a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                } else {
1408a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                    Arrays.fill(mLayouts, null);
1409c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian                }
141078a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian
141178a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian                if (mAllDayLayouts == null || mAllDayLayouts.length < mAllDayEvents.size()) {
141278a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian                    mAllDayLayouts = new StaticLayout[events.size()];
141378a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian                } else {
141478a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian                    Arrays.fill(mAllDayLayouts, null);
141578a677bbb5fa115fa0995b5783adeeefad67167eFariborz Jahanian                }
1416c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian
1417a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                mRemeasure = true;
1418a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                mComputeSelectedEvents = true;
1419a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                recalc();
1420a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian//                mContext.stopProgressSpinner();
1421a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian                invalidate();
1422a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian            }
1423c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian        }, mCancelCallback);
1424c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian    }
1425a0f55792409289d1d343023fa8292cff6355e538Fariborz Jahanian
1426c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian    @Override
1427c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian    protected void onDraw(Canvas canvas) {
1428c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian        if (mRemeasure) {
1429c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian            remeasure(getWidth(), getHeight());
1430c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian            mRemeasure = false;
1431c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian        }
1432c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian        canvas.save();
1433fa3ee8e6776634caf064ba5928ca7699d317a280Fariborz Jahanian
1434fa3ee8e6776634caf064ba5928ca7699d317a280Fariborz Jahanian        float yTranslate = -mViewStartY + DAY_HEADER_HEIGHT + mAllDayHeight;
1435fa3ee8e6776634caf064ba5928ca7699d317a280Fariborz Jahanian        // offset canvas by the current drag and header position
1436c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian        canvas.translate(-mViewStartX, yTranslate);
1437e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        // clip to everything below the allDay area
1438e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop        Rect dest = mDestRect;
1439c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian        dest.top = (int) (mFirstCell - yTranslate);
1440c385c90c68dfa376650e2facfbb444b2ec9bd110Fariborz Jahanian        dest.bottom = (int) (mViewHeight - yTranslate);
1441a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        dest.left = 0;
1442a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        dest.right = mViewWidth;
144339f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        canvas.save();
144439f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        canvas.clipRect(dest);
144539f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        // Draw the movable part of the view
1446a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        doDraw(canvas);
1447a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek        // restore to having no clip
144839f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        canvas.restore();
144939f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian
145039f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian        if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
1451e0def7589a8afa8a6acef13476bb3f882c104b91Chris Lattner            float xTranslate;
1452e0def7589a8afa8a6acef13476bb3f882c104b91Chris Lattner            if (mViewStartX > 0) {
145339f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian                xTranslate = mViewWidth;
145420ebf8f15df48f27ce2bf7c1b3ae87565235600fSteve Naroff            } else {
145520ebf8f15df48f27ce2bf7c1b3ae87565235600fSteve Naroff                xTranslate = -mViewWidth;
145620ebf8f15df48f27ce2bf7c1b3ae87565235600fSteve Naroff            }
1457db6ed1786bf460e1143f67d14bf2d71ad9856f81Chris Lattner            // Move the canvas around to prep it for the next view
1458db6ed1786bf460e1143f67d14bf2d71ad9856f81Chris Lattner            // specifically, shift it by a screen and undo the
145939f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // yTranslation which will be redone in the nextView's onDraw().
146039f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            canvas.translate(xTranslate, -yTranslate);
146139f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            DayView nextView = (DayView) mViewSwitcher.getNextView();
1462a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek
146339f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // Prevent infinite recursive calls to onDraw().
1464a526c5c67e5a0473c340903ee542ce570119665fTed Kremenek            nextView.mTouchMode = TOUCH_MODE_INITIAL_STATE;
146539f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian
146639f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            nextView.onDraw(canvas);
146739f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // Move it back for this view
14685bdd4e300b432246b9d261702828e9875414128bTed Kremenek            canvas.translate(-xTranslate, 0);
14695bdd4e300b432246b9d261702828e9875414128bTed Kremenek        } else {
1470e2563ca02a519c2ad6d64dfed87d6e86c5d3c072Sam Bishop            // If we drew another view we already translated it back
147139f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // If we didn't draw another view we should be at the edge of the
147239f8f159c488a900e5958d5aab3e467af9ec8a2bFariborz Jahanian            // screen
14734b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl            canvas.translate(mViewStartX, -yTranslate);
14744b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        }
14754b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
14764b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        // Draw the fixed areas (that don't scroll) directly to the canvas.
14774b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        drawAfterScroll(canvas);
14784b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        mComputeSelectedEvents = false;
14794b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        canvas.restore();
14804b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl    }
14814b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
14824b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl    private void drawAfterScroll(Canvas canvas) {
14834b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        Paint p = mPaint;
14844b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        Rect r = mRect;
14854b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
14864b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        if (mMaxAllDayEvents != 0) {
14874b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl            drawAllDayEvents(mFirstJulianDay, mNumDays, canvas, p);
14884b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl//            drawUpperLeftCorner(r, canvas, p);
14894b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        }
14904b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
14914b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        drawScrollLine(r, canvas, p);
14924b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
14934b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        drawDayHeaderLoop(r, canvas, p);
14944b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
14954b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        // Draw the AM and PM indicators if we're in 12 hour mode
14964b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        if (!mIs24HourFormat) {
14974b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl            drawAmPm(canvas, p);
14984b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        }
14994b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
15004b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        // Update the popup window showing the event details, but only if
15014b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        // we are not scrolling and we have focus.
15024b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        if (!mScrolling && isFocused()) {
15034b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl            updateEventDetails();
15044b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl        }
15054b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl    }
15064b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl
15074b07b2968f87f3cd5a3d8c76145f1cbfd718d42dSebastian Redl    // This isn't really the upper-left corner.  It's the square area just
15088351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl    // below the upper-left corner, above the hours and to the left of the
15098351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl    // all-day area.
15108351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//    private void drawUpperLeftCorner(Rect r, Canvas canvas, Paint p) {
15118351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//        p.setColor(mCalendarHourBackground);
15128351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//        r.top = DAY_HEADER_HEIGHT;
15138351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//        r.bottom = r.top + mAllDayHeight + ALLDAY_TOP_MARGIN;
15148351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//        r.left = 0;
15158351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//        r.right = mHoursWidth;
15168351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//        canvas.drawRect(r, p);
15178351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl//    }
15188351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl
15198351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl    private void drawScrollLine(Rect r, Canvas canvas, Paint p) {
15208351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        p.setColor(mCalendarGridLineInnerHorizontalColor);
15218351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        p.setAntiAlias(false);
15228351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl
15238351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        int y = mFirstCell - 1;
15248351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        canvas.drawLine(0, y, mHoursWidth + (mCellWidth + DAY_GAP) * mNumDays, y, p);
15258351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        p.setAntiAlias(true);
15268351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl    }
15278351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl
15288351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl    private void drawDayHeaderLoop(Rect r, Canvas canvas, Paint p) {
15298351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // Draw the horizontal day background banner
15308351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // p.setColor(mCalendarDateBannerBackground);
15318351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // r.top = 0;
15328351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // r.bottom = DAY_HEADER_HEIGHT;
15338351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // r.left = 0;
15348351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // r.right = mHoursWidth + mNumDays * (mCellWidth + DAY_GAP);
15358351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // canvas.drawRect(r, p);
15368351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        //
15378351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // Fill the extra space on the right side with the default background
15388351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // r.left = r.right;
15398351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // r.right = mViewWidth;
15408351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // p.setColor(mCalendarGridAreaBackground);
15418351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        // canvas.drawRect(r, p);
15428351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl
15438351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        int todayNum = mTodayJulianDay - mFirstJulianDay;
15448351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl        if (mNumDays > 1) {
15458351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl            r.top = 0;
15468351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl            r.bottom = DAY_HEADER_HEIGHT;
15478351da06ca3082dfd49dd8e3c1785a986920f57cSebastian Redl
15485f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            // Highlight today
15495f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer            if (mFirstJulianDay <= mTodayJulianDay
15505f016e2cb5d11daeb237544de1c5d59f20fe1a6eReid Spencer                    && mTodayJulianDay < (mFirstJulianDay + mNumDays)) {
1551                r.left = mHoursWidth + todayNum * (mCellWidth + DAY_GAP);
1552                r.right = r.left + mCellWidth;
1553                mTodayHeaderDrawable.setBounds(r);
1554                mTodayHeaderDrawable.draw(canvas);
1555            }
1556
1557            // Draw a highlight on the selected day (if any), but only if we are
1558            // displaying more than one day.
1559            //
1560            // int selectedDayNum = mSelectionDay - mFirstJulianDay;
1561            // if (mSelectionMode != SELECTION_HIDDEN && selectedDayNum >= 0
1562            // && selectedDayNum < mNumDays) {
1563            // p.setColor(mCalendarDateSelected);
1564            // r.left = mHoursWidth + selectedDayNum * (mCellWidth + DAY_GAP);
1565            // r.right = r.left + mCellWidth;
1566            // canvas.drawRect(r, p);
1567            // }
1568        }
1569
1570        p.setTypeface(mBold);
1571        p.setTextAlign(Paint.Align.RIGHT);
1572        float x = mHoursWidth;
1573        int deltaX = mCellWidth + DAY_GAP;
1574        int cell = mFirstJulianDay;
1575
1576        String[] dayNames;
1577        if (mDateStrWidth < mCellWidth) {
1578            dayNames = mDayStrs;
1579        } else {
1580            dayNames = mDayStrs2Letter;
1581        }
1582
1583        p.setAntiAlias(true);
1584        for (int day = 0; day < mNumDays; day++, cell++) {
1585            int dayOfWeek = day + mFirstVisibleDayOfWeek;
1586            if (dayOfWeek >= 14) {
1587                dayOfWeek -= 14;
1588            }
1589
1590            int color = mCalendarDateBannerTextColor;
1591            if (mNumDays == 1) {
1592                if (dayOfWeek == Time.SATURDAY) {
1593                    color = mWeek_saturdayColor;
1594                } else if (dayOfWeek == Time.SUNDAY) {
1595                    color = mWeek_sundayColor;
1596                }
1597            } else {
1598                final int column = day % 7;
1599                if (Utils.isSaturday(column, mFirstDayOfWeek)) {
1600                    color = mWeek_saturdayColor;
1601                } else if (Utils.isSunday(column, mFirstDayOfWeek)) {
1602                    color = mWeek_sundayColor;
1603                }
1604            }
1605
1606            color &= 0x00FFFFFF;
1607            if (todayNum == day) {
1608                color |= DAY_HEADER_TODAY_ALPHA;
1609            } else {
1610                color |= DAY_HEADER_ALPHA;
1611            }
1612
1613            p.setColor(color);
1614            drawDayHeader(dayNames[dayOfWeek], day, cell, x, canvas, p);
1615            x += deltaX;
1616        }
1617        p.setTypeface(null);
1618    }
1619
1620    private void drawAmPm(Canvas canvas, Paint p) {
1621        p.setColor(mCalendarAmPmLabel);
1622        p.setTextSize(AMPM_FONT_SIZE);
1623        p.setTypeface(mBold);
1624        p.setAntiAlias(true);
1625        mPaint.setTextAlign(Paint.Align.RIGHT);
1626        String text = mAmString;
1627        if (mFirstHour >= 12) {
1628            text = mPmString;
1629        }
1630        int y = mFirstCell + mFirstHourOffset + 2 * mHoursTextHeight + HOUR_GAP;
1631        int right = mHoursWidth - HOURS_RIGHT_MARGIN;
1632        canvas.drawText(text, right, y, p);
1633
1634        if (mFirstHour < 12 && mFirstHour + mNumHours > 12) {
1635            // Also draw the "PM"
1636            text = mPmString;
1637            y = mFirstCell + mFirstHourOffset + (12 - mFirstHour) * (mCellHeight + HOUR_GAP)
1638                    + 2 * mHoursTextHeight + HOUR_GAP;
1639            canvas.drawText(text, right, y, p);
1640        }
1641    }
1642
1643    private void drawCurrentTimeLine(Rect r, final int left, final int top, Canvas canvas,
1644            Paint p) {
1645        r.left = left - CURRENT_TIME_LINE_SIDE_BUFFER;
1646        r.right = left + mCellWidth + DAY_GAP + CURRENT_TIME_LINE_SIDE_BUFFER;
1647
1648        r.top = top - mCurrentTimeLine.getIntrinsicHeight() / 2;
1649        r.bottom = r.top + mCurrentTimeLine.getIntrinsicHeight();
1650
1651        mCurrentTimeLine.setBounds(r);
1652        mCurrentTimeLine.draw(canvas);
1653    }
1654
1655    private void doDraw(Canvas canvas) {
1656        Paint p = mPaint;
1657        Rect r = mRect;
1658
1659        drawGridBackground(r, canvas, p);
1660        drawHours(r, canvas, p);
1661
1662        // Draw each day
1663        int x = mHoursWidth;
1664        int deltaX = mCellWidth + DAY_GAP;
1665        int cell = mFirstJulianDay;
1666        for (int day = 0; day < mNumDays; day++, cell++) {
1667            // TODO Wow, this needs cleanup. drawEvents loop through all the
1668            // events on every call.
1669            drawEvents(cell, x, HOUR_GAP, canvas, p);
1670            //If this is today
1671            if(cell == mTodayJulianDay) {
1672                int lineY = mCurrentTime.hour * (mCellHeight + HOUR_GAP)
1673                        + ((mCurrentTime.minute * mCellHeight) / 60) + 1;
1674
1675                //And the current time shows up somewhere on the screen
1676                if(lineY >= mViewStartY && lineY < mViewStartY + mViewHeight - 2) {
1677                    drawCurrentTimeLine(r, x, lineY, canvas, p);
1678                }
1679            }
1680            x += deltaX;
1681        }
1682    }
1683
1684    private void drawHours(Rect r, Canvas canvas, Paint p) {
1685        // Comment out as the background will be a drawable
1686
1687        // Draw the background for the hour labels
1688        // p.setColor(mCalendarHourBackground);
1689        // r.top = 0;
1690        // r.bottom = 24 * (mCellHeight + HOUR_GAP) + HOUR_GAP;
1691        // r.left = 0;
1692        // r.right = mHoursWidth;
1693        // canvas.drawRect(r, p);
1694
1695        // Fill the bottom left corner with the default grid background
1696        // r.top = r.bottom;
1697        // r.bottom = mBitmapHeight;
1698        // p.setColor(mCalendarGridAreaBackground);
1699        // canvas.drawRect(r, p);
1700
1701        // Draw a highlight on the selected hour (if needed)
1702        if (mSelectionMode != SELECTION_HIDDEN && !mSelectionAllDay) {
1703            // p.setColor(mCalendarHourSelected);
1704            int daynum = mSelectionDay - mFirstJulianDay;
1705            r.top = mSelectionHour * (mCellHeight + HOUR_GAP);
1706            r.bottom = r.top + mCellHeight + HOUR_GAP;
1707            r.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
1708            r.right = r.left + mCellWidth + DAY_GAP;
1709
1710            // Draw a border around the highlighted grid hour.
1711            // drawEmptyRect(canvas, r, mSelectionPaint.getColor());
1712            saveSelectionPosition(r.left, r.top, r.right, r.bottom);
1713
1714            // Also draw the highlight on the grid
1715            p.setColor(mCalendarGridAreaSelected);
1716            r.top += HOUR_GAP;
1717            r.right -= DAY_GAP;
1718            canvas.drawRect(r, p);
1719        }
1720
1721        p.setColor(mCalendarHourLabel);
1722        p.setTextSize(HOURS_FONT_SIZE);
1723        p.setTypeface(mBold);
1724        p.setTextAlign(Paint.Align.RIGHT);
1725        p.setAntiAlias(true);
1726
1727        int right = mHoursWidth - HOURS_RIGHT_MARGIN;
1728        int y = HOUR_GAP + mHoursTextHeight;
1729
1730        for (int i = 0; i < 24; i++) {
1731            String time = mHourStrs[i];
1732            canvas.drawText(time, right, y, p);
1733            y += mCellHeight + HOUR_GAP;
1734        }
1735    }
1736
1737    private void drawDayHeader(String dayStr, int day, int cell, float x, Canvas canvas, Paint p) {
1738        int dateNum = mFirstVisibleDate + day;
1739        if (dateNum > mMonthLength) {
1740            dateNum -= mMonthLength;
1741        }
1742
1743        // Draw day of the month
1744        String dateNumStr = String.valueOf(dateNum);
1745        if (mNumDays > 1) {
1746            float y = DAY_HEADER_HEIGHT - DAY_HEADER_BOTTOM_MARGIN;
1747
1748            // Draw day of the month
1749            x += mCellWidth - DAY_HEADER_RIGHT_MARGIN;
1750            p.setTextSize(DATE_HEADER_FONT_SIZE);
1751            canvas.drawText(dateNumStr, x, y, p);
1752
1753            // Draw day of the week
1754            x -= p.measureText(dateNumStr) + DAY_HEADER_LEFT_MARGIN;
1755            p.setTextSize(DAY_HEADER_FONT_SIZE);
1756            canvas.drawText(dayStr, x, y, p);
1757        } else {
1758            float y = DAY_HEADER_HEIGHT - DAY_HEADER_ONE_DAY_BOTTOM_MARGIN;
1759            p.setTextAlign(Paint.Align.LEFT);
1760
1761            // Draw day of the week
1762            x += DAY_HEADER_ONE_DAY_LEFT_MARGIN;
1763            p.setTextSize(DAY_HEADER_FONT_SIZE);
1764            canvas.drawText(dayStr, x, y, p);
1765
1766            // Draw day of the month
1767            x += p.measureText(dayStr) + DAY_HEADER_ONE_DAY_RIGHT_MARGIN;
1768            p.setTextSize(DATE_HEADER_FONT_SIZE);
1769            canvas.drawText(dateNumStr, x, y, p);
1770        }
1771    }
1772
1773    private void drawGridBackground(Rect r, Canvas canvas, Paint p) {
1774        Paint.Style savedStyle = p.getStyle();
1775
1776        // Draw the outer horizontal grid lines
1777        p.setColor(mCalendarGridLineHorizontalColor);
1778        p.setStyle(Style.FILL);
1779
1780        p.setAntiAlias(false);
1781        final float startX = 0;
1782        final float stopX = mHoursWidth + (mCellWidth + DAY_GAP) * mNumDays;
1783        float y = 0;
1784        final float deltaY = mCellHeight + HOUR_GAP;
1785        p.setStrokeWidth(GRID_LINE_WIDTH);
1786        int linesIndex = 0;
1787        for (int hour = 0; hour <= 24; hour++) {
1788            mLines[linesIndex++] = startX;
1789            mLines[linesIndex++] = y;
1790            mLines[linesIndex++] = stopX;
1791            mLines[linesIndex++] = y;
1792            y += deltaY;
1793        }
1794        if (mCalendarGridLineVerticalColor != mCalendarGridLineHorizontalColor) {
1795            canvas.drawLines(mLines, 0, linesIndex, p);
1796            linesIndex = 0;
1797            p.setColor(mCalendarGridLineVerticalColor);
1798        }
1799
1800        // Draw the outer vertical grid lines
1801        final float startY = 0;
1802        final float stopY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP);
1803        final float deltaX = mCellWidth + DAY_GAP;
1804        float x = mHoursWidth;
1805        for (int day = 0; day < mNumDays; day++) {
1806            x += deltaX;
1807            mLines[linesIndex++] = x;
1808            mLines[linesIndex++] = startY;
1809            mLines[linesIndex++] = x;
1810            mLines[linesIndex++] = stopY;
1811        }
1812        canvas.drawLines(mLines, 0, linesIndex, p);
1813
1814        // Draw the inner horizontal grid lines
1815        p.setColor(mCalendarGridLineInnerHorizontalColor);
1816        p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
1817        y = 0;
1818        linesIndex = 0;
1819        for (int hour = 0; hour <= 24; hour++) {
1820            mLines[linesIndex++] = startX;
1821            mLines[linesIndex++] = y;
1822            mLines[linesIndex++] = stopX;
1823            mLines[linesIndex++] = y;
1824            y += deltaY;
1825        }
1826        if (mCalendarGridLineInnerVerticalColor != mCalendarGridLineInnerHorizontalColor) {
1827            canvas.drawLines(mLines, 0, linesIndex, p);
1828            linesIndex = 0;
1829            p.setColor(mCalendarGridLineInnerVerticalColor);
1830        }
1831
1832        // Draw the inner vertical grid lines
1833        x = mHoursWidth;
1834        for (int day = 0; day < mNumDays; day++) {
1835            x += deltaX;
1836            mLines[linesIndex++] = x;
1837            mLines[linesIndex++] = startY;
1838            mLines[linesIndex++] = x;
1839            mLines[linesIndex++] = stopY;
1840        }
1841        canvas.drawLines(mLines, 0, linesIndex, p);
1842
1843        // Restore the saved style.
1844        p.setStyle(savedStyle);
1845        p.setAntiAlias(true);
1846    }
1847
1848    Event getSelectedEvent() {
1849        if (mSelectedEvent == null) {
1850            // There is no event at the selected hour, so create a new event.
1851            return getNewEvent(mSelectionDay, getSelectedTimeInMillis(),
1852                    getSelectedMinutesSinceMidnight());
1853        }
1854        return mSelectedEvent;
1855    }
1856
1857    boolean isEventSelected() {
1858        return (mSelectedEvent != null);
1859    }
1860
1861    Event getNewEvent() {
1862        return getNewEvent(mSelectionDay, getSelectedTimeInMillis(),
1863                getSelectedMinutesSinceMidnight());
1864    }
1865
1866    static Event getNewEvent(int julianDay, long utcMillis,
1867            int minutesSinceMidnight) {
1868        Event event = Event.newInstance();
1869        event.startDay = julianDay;
1870        event.endDay = julianDay;
1871        event.startMillis = utcMillis;
1872        event.endMillis = event.startMillis + MILLIS_PER_HOUR;
1873        event.startTime = minutesSinceMidnight;
1874        event.endTime = event.startTime + MINUTES_PER_HOUR;
1875        return event;
1876    }
1877
1878    private int computeMaxStringWidth(int currentMax, String[] strings, Paint p) {
1879        float maxWidthF = 0.0f;
1880
1881        int len = strings.length;
1882        for (int i = 0; i < len; i++) {
1883            float width = p.measureText(strings[i]);
1884            maxWidthF = Math.max(width, maxWidthF);
1885        }
1886        int maxWidth = (int) (maxWidthF + 0.5);
1887        if (maxWidth < currentMax) {
1888            maxWidth = currentMax;
1889        }
1890        return maxWidth;
1891    }
1892
1893    private void saveSelectionPosition(float left, float top, float right, float bottom) {
1894        mPrevBox.left = (int) left;
1895        mPrevBox.right = (int) right;
1896        mPrevBox.top = (int) top;
1897        mPrevBox.bottom = (int) bottom;
1898    }
1899
1900    private Rect getCurrentSelectionPosition() {
1901        Rect box = new Rect();
1902        box.top = mSelectionHour * (mCellHeight + HOUR_GAP);
1903        box.bottom = box.top + mCellHeight + HOUR_GAP;
1904        int daynum = mSelectionDay - mFirstJulianDay;
1905        box.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
1906        box.right = box.left + mCellWidth + DAY_GAP;
1907        return box;
1908    }
1909
1910    private void setupTextRect(Rect r) {
1911        if (r.bottom <= r.top || r.right <= r.left) {
1912            r.bottom = r.top;
1913            r.right = r.left;
1914            return;
1915        }
1916
1917        if (r.bottom - r.top > EVENT_TEXT_TOP_MARGIN + EVENT_TEXT_BOTTOM_MARGIN) {
1918            r.top += EVENT_TEXT_TOP_MARGIN;
1919            r.bottom -= EVENT_TEXT_BOTTOM_MARGIN;
1920        }
1921        if (r.right - r.left > EVENT_TEXT_LEFT_MARGIN + EVENT_TEXT_RIGHT_MARGIN) {
1922            r.left += EVENT_TEXT_LEFT_MARGIN;
1923            r.right -= EVENT_TEXT_RIGHT_MARGIN;
1924        }
1925    }
1926
1927    private void setupAllDayTextRect(Rect r) {
1928        if (r.bottom <= r.top || r.right <= r.left) {
1929            r.bottom = r.top;
1930            r.right = r.left;
1931            return;
1932        }
1933
1934        if (r.bottom - r.top > EVENT_ALL_DAY_TEXT_TOP_MARGIN + EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN) {
1935            r.top += EVENT_ALL_DAY_TEXT_TOP_MARGIN;
1936            r.bottom -= EVENT_ALL_DAY_TEXT_BOTTOM_MARGIN;
1937        }
1938        if (r.right - r.left > EVENT_ALL_DAY_TEXT_LEFT_MARGIN + EVENT_ALL_DAY_TEXT_RIGHT_MARGIN) {
1939            r.left += EVENT_ALL_DAY_TEXT_LEFT_MARGIN;
1940            r.right -= EVENT_ALL_DAY_TEXT_RIGHT_MARGIN;
1941        }
1942    }
1943
1944    /**
1945     * Return the layout for a numbered event. Create it if not already existing
1946     */
1947    private StaticLayout getEventLayout(StaticLayout[] layouts, int i, Event event, Paint paint,
1948            Rect r) {
1949        if (i < 0 || i >= layouts.length) {
1950            return null;
1951        }
1952
1953        StaticLayout layout = layouts[i];
1954        // Check if we have already initialized the StaticLayout and that
1955        // the width hasn't changed (due to vertical resizing which causes
1956        // re-layout of events at min height)
1957        if (layout == null || r.width() != layout.getWidth()) {
1958            String text = drawTextSanitizer(event.getTitleAndLocation(), MAX_EVENT_TEXT_LEN);
1959
1960            // Leave a one pixel boundary on the left and right of the rectangle for the event
1961            layout = new StaticLayout(text, 0, text.length(), new TextPaint(paint), r.width(),
1962                    Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true, null, r.width());
1963
1964            layouts[i] = layout;
1965        }
1966
1967        return layout;
1968    }
1969
1970    private void drawAllDayEvents(int firstDay, int numDays, Canvas canvas, Paint p) {
1971        if (mSelectionAllDay) {
1972            // Draw the highlight on the selected all-day area
1973            mRect.top = DAY_HEADER_HEIGHT + 1;
1974            mRect.bottom = mRect.top + mAllDayHeight + ALLDAY_TOP_MARGIN - 2;
1975            int daynum = mSelectionDay - mFirstJulianDay;
1976            mRect.left = mHoursWidth + daynum * (mCellWidth + DAY_GAP);
1977            mRect.right = mRect.left + mCellWidth + DAY_GAP - 1;
1978            p.setColor(mCalendarGridAreaSelected);
1979            canvas.drawRect(mRect, p);
1980        }
1981
1982        p.setTextSize(NORMAL_FONT_SIZE);
1983        p.setTextAlign(Paint.Align.LEFT);
1984        Paint eventTextPaint = mEventTextPaint;
1985
1986        // Draw the background for the all-day events area
1987        // r.top = DAY_HEADER_HEIGHT;
1988        // r.bottom = r.top + mAllDayHeight + ALLDAY_TOP_MARGIN;
1989        // r.left = mHoursWidth;
1990        // r.right = r.left + mNumDays * (mCellWidth + DAY_GAP);
1991        // p.setColor(mCalendarAllDayBackground);
1992        // canvas.drawRect(r, p);
1993
1994        // Fill the extra space on the right side with the default background
1995        // r.left = r.right;
1996        // r.right = mViewWidth;
1997        // p.setColor(mCalendarGridAreaBackground);
1998        // canvas.drawRect(r, p);
1999
2000        // Draw the outer vertical grid lines
2001        p.setColor(mCalendarGridLineVerticalColor);
2002        p.setStyle(Style.FILL);
2003        p.setStrokeWidth(GRID_LINE_WIDTH);
2004        p.setAntiAlias(false);
2005        final float startY = DAY_HEADER_HEIGHT;
2006        final float stopY = startY + mAllDayHeight + ALLDAY_TOP_MARGIN;
2007        final float deltaX = mCellWidth + DAY_GAP;
2008        float x = mHoursWidth;
2009        int linesIndex = 0;
2010        // Line bounding the top of the all day area
2011        mLines[linesIndex++] = 0;
2012        mLines[linesIndex++] = startY;
2013        mLines[linesIndex++] = mHoursWidth + deltaX * mNumDays;
2014        mLines[linesIndex++] = startY;
2015
2016        for (int day = 0; day < mNumDays; day++) {
2017            x += deltaX;
2018            mLines[linesIndex++] = x;
2019            mLines[linesIndex++] = startY;
2020            mLines[linesIndex++] = x;
2021            mLines[linesIndex++] = stopY;
2022        }
2023        canvas.drawLines(mLines, 0, linesIndex, p);
2024
2025        // Draw the inner vertical grid lines
2026        p.setColor(mCalendarGridLineInnerVerticalColor);
2027        x = mHoursWidth;
2028        p.setStrokeWidth(GRID_LINE_INNER_WIDTH);
2029        linesIndex = 0;
2030        // Line bounding the top of the all day area
2031        mLines[linesIndex++] = 0;
2032        mLines[linesIndex++] = startY;
2033        mLines[linesIndex++] = mHoursWidth + (deltaX) * mNumDays;
2034        mLines[linesIndex++] = startY;
2035
2036        for (int day = 0; day < mNumDays; day++) {
2037            x += deltaX;
2038            mLines[linesIndex++] = x;
2039            mLines[linesIndex++] = startY;
2040            mLines[linesIndex++] = x;
2041            mLines[linesIndex++] = stopY;
2042        }
2043        canvas.drawLines(mLines, 0, linesIndex, p);
2044
2045        p.setAntiAlias(true);
2046        p.setStyle(Style.FILL);
2047
2048        int y = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
2049        float left = mHoursWidth;
2050        int lastDay = firstDay + numDays - 1;
2051        final ArrayList<Event> events = mAllDayEvents;
2052        int numEvents = events.size();
2053        float drawHeight = mAllDayHeight;
2054        float numRectangles = mMaxAllDayEvents;
2055        for (int i = 0; i < numEvents; i++) {
2056            Event event = events.get(i);
2057            int startDay = event.startDay;
2058            int endDay = event.endDay;
2059            if (startDay > lastDay || endDay < firstDay) {
2060                continue;
2061            }
2062            if (startDay < firstDay) {
2063                startDay = firstDay;
2064            }
2065            if (endDay > lastDay) {
2066                endDay = lastDay;
2067            }
2068            int startIndex = startDay - firstDay;
2069            int endIndex = endDay - firstDay;
2070            float height = drawHeight / numRectangles;
2071
2072            // Prevent a single event from getting too big
2073            if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
2074                height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
2075            }
2076
2077            // Leave a one-pixel space between the vertical day lines and the
2078            // event rectangle.
2079            event.left = left + startIndex * (mCellWidth + DAY_GAP);
2080            event.right = left + endIndex * (mCellWidth + DAY_GAP) + mCellWidth;
2081            event.top = y + height * event.getColumn();
2082            event.bottom = event.top + height;
2083
2084            Rect r = drawEventRect(event, canvas, p, eventTextPaint);
2085            setupAllDayTextRect(r);
2086            StaticLayout layout = getEventLayout(mAllDayLayouts, i, event, eventTextPaint, r);
2087            drawEventText(layout, r, canvas, r.top, r.bottom);
2088
2089            // Check if this all-day event intersects the selected day
2090            if (mSelectionAllDay && mComputeSelectedEvents) {
2091                if (startDay <= mSelectionDay && endDay >= mSelectionDay) {
2092                    mSelectedEvents.add(event);
2093                }
2094            }
2095        }
2096
2097        if (mSelectionAllDay) {
2098            // Compute the neighbors for the list of all-day events that
2099            // intersect the selected day.
2100            computeAllDayNeighbors();
2101
2102            // Set the selection position to zero so that when we move down
2103            // to the normal event area, we will highlight the topmost event.
2104            saveSelectionPosition(0f, 0f, 0f, 0f);
2105        }
2106    }
2107
2108    private void computeAllDayNeighbors() {
2109        int len = mSelectedEvents.size();
2110        if (len == 0 || mSelectedEvent != null) {
2111            return;
2112        }
2113
2114        // First, clear all the links
2115        for (int ii = 0; ii < len; ii++) {
2116            Event ev = mSelectedEvents.get(ii);
2117            ev.nextUp = null;
2118            ev.nextDown = null;
2119            ev.nextLeft = null;
2120            ev.nextRight = null;
2121        }
2122
2123        // For each event in the selected event list "mSelectedEvents", find
2124        // its neighbors in the up and down directions.  This could be done
2125        // more efficiently by sorting on the Event.getColumn() field, but
2126        // the list is expected to be very small.
2127
2128        // Find the event in the same row as the previously selected all-day
2129        // event, if any.
2130        int startPosition = -1;
2131        if (mPrevSelectedEvent != null && mPrevSelectedEvent.allDay) {
2132            startPosition = mPrevSelectedEvent.getColumn();
2133        }
2134        int maxPosition = -1;
2135        Event startEvent = null;
2136        Event maxPositionEvent = null;
2137        for (int ii = 0; ii < len; ii++) {
2138            Event ev = mSelectedEvents.get(ii);
2139            int position = ev.getColumn();
2140            if (position == startPosition) {
2141                startEvent = ev;
2142            } else if (position > maxPosition) {
2143                maxPositionEvent = ev;
2144                maxPosition = position;
2145            }
2146            for (int jj = 0; jj < len; jj++) {
2147                if (jj == ii) {
2148                    continue;
2149                }
2150                Event neighbor = mSelectedEvents.get(jj);
2151                int neighborPosition = neighbor.getColumn();
2152                if (neighborPosition == position - 1) {
2153                    ev.nextUp = neighbor;
2154                } else if (neighborPosition == position + 1) {
2155                    ev.nextDown = neighbor;
2156                }
2157            }
2158        }
2159        if (startEvent != null) {
2160            mSelectedEvent = startEvent;
2161        } else {
2162            mSelectedEvent = maxPositionEvent;
2163        }
2164    }
2165
2166    private void drawEvents(int date, int left, int top, Canvas canvas, Paint p) {
2167        Paint eventTextPaint = mEventTextPaint;
2168        int cellWidth = mCellWidth;
2169        int cellHeight = mCellHeight;
2170
2171        // Use the selected hour as the selection region
2172        Rect selectionArea = mRect;
2173        selectionArea.top = top + mSelectionHour * (cellHeight + HOUR_GAP);
2174        selectionArea.bottom = selectionArea.top + cellHeight;
2175        selectionArea.left = left;
2176        selectionArea.right = selectionArea.left + cellWidth;
2177
2178        final ArrayList<Event> events = mEvents;
2179        int numEvents = events.size();
2180        EventGeometry geometry = mEventGeometry;
2181
2182        final int viewEndY = mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAllDayHeight;
2183        for (int i = 0; i < numEvents; i++) {
2184            Event event = events.get(i);
2185            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
2186                continue;
2187            }
2188
2189            // Don't draw it if it is not visible
2190            if (event.bottom < mViewStartY || event.top > viewEndY) {
2191                continue;
2192            }
2193
2194            if (date == mSelectionDay && !mSelectionAllDay && mComputeSelectedEvents
2195                    && geometry.eventIntersectsSelection(event, selectionArea)) {
2196                mSelectedEvents.add(event);
2197            }
2198
2199            Rect r = drawEventRect(event, canvas, p, eventTextPaint);
2200            setupTextRect(r);
2201
2202            // Don't draw text if it is not visible
2203            if (r.top > viewEndY || r.bottom < mViewStartY) {
2204                continue;
2205            }
2206            StaticLayout layout = getEventLayout(mLayouts, i, event, eventTextPaint, r);
2207            // TODO: not sure why we are 4 pixels off
2208            drawEventText(layout, r, canvas, mViewStartY + 4,
2209                    mViewStartY + mViewHeight - DAY_HEADER_HEIGHT - mAllDayHeight);
2210        }
2211
2212        if (date == mSelectionDay && !mSelectionAllDay && isFocused()
2213                && mSelectionMode != SELECTION_HIDDEN) {
2214            computeNeighbors();
2215        }
2216    }
2217
2218    // Computes the "nearest" neighbor event in four directions (left, right,
2219    // up, down) for each of the events in the mSelectedEvents array.
2220    private void computeNeighbors() {
2221        int len = mSelectedEvents.size();
2222        if (len == 0 || mSelectedEvent != null) {
2223            return;
2224        }
2225
2226        // First, clear all the links
2227        for (int ii = 0; ii < len; ii++) {
2228            Event ev = mSelectedEvents.get(ii);
2229            ev.nextUp = null;
2230            ev.nextDown = null;
2231            ev.nextLeft = null;
2232            ev.nextRight = null;
2233        }
2234
2235        Event startEvent = mSelectedEvents.get(0);
2236        int startEventDistance1 = 100000;  // any large number
2237        int startEventDistance2 = 100000;  // any large number
2238        int prevLocation = FROM_NONE;
2239        int prevTop;
2240        int prevBottom;
2241        int prevLeft;
2242        int prevRight;
2243        int prevCenter = 0;
2244        Rect box = getCurrentSelectionPosition();
2245        if (mPrevSelectedEvent != null) {
2246            prevTop = (int) mPrevSelectedEvent.top;
2247            prevBottom = (int) mPrevSelectedEvent.bottom;
2248            prevLeft = (int) mPrevSelectedEvent.left;
2249            prevRight = (int) mPrevSelectedEvent.right;
2250            // Check if the previously selected event intersects the previous
2251            // selection box.  (The previously selected event may be from a
2252            // much older selection box.)
2253            if (prevTop >= mPrevBox.bottom || prevBottom <= mPrevBox.top
2254                    || prevRight <= mPrevBox.left || prevLeft >= mPrevBox.right) {
2255                mPrevSelectedEvent = null;
2256                prevTop = mPrevBox.top;
2257                prevBottom = mPrevBox.bottom;
2258                prevLeft = mPrevBox.left;
2259                prevRight = mPrevBox.right;
2260            } else {
2261                // Clip the top and bottom to the previous selection box.
2262                if (prevTop < mPrevBox.top) {
2263                    prevTop = mPrevBox.top;
2264                }
2265                if (prevBottom > mPrevBox.bottom) {
2266                    prevBottom = mPrevBox.bottom;
2267                }
2268            }
2269        } else {
2270            // Just use the previously drawn selection box
2271            prevTop = mPrevBox.top;
2272            prevBottom = mPrevBox.bottom;
2273            prevLeft = mPrevBox.left;
2274            prevRight = mPrevBox.right;
2275        }
2276
2277        // Figure out where we came from and compute the center of that area.
2278        if (prevLeft >= box.right) {
2279            // The previously selected event was to the right of us.
2280            prevLocation = FROM_RIGHT;
2281            prevCenter = (prevTop + prevBottom) / 2;
2282        } else if (prevRight <= box.left) {
2283            // The previously selected event was to the left of us.
2284            prevLocation = FROM_LEFT;
2285            prevCenter = (prevTop + prevBottom) / 2;
2286        } else if (prevBottom <= box.top) {
2287            // The previously selected event was above us.
2288            prevLocation = FROM_ABOVE;
2289            prevCenter = (prevLeft + prevRight) / 2;
2290        } else if (prevTop >= box.bottom) {
2291            // The previously selected event was below us.
2292            prevLocation = FROM_BELOW;
2293            prevCenter = (prevLeft + prevRight) / 2;
2294        }
2295
2296        // For each event in the selected event list "mSelectedEvents", search
2297        // all the other events in that list for the nearest neighbor in 4
2298        // directions.
2299        for (int ii = 0; ii < len; ii++) {
2300            Event ev = mSelectedEvents.get(ii);
2301
2302            int startTime = ev.startTime;
2303            int endTime = ev.endTime;
2304            int left = (int) ev.left;
2305            int right = (int) ev.right;
2306            int top = (int) ev.top;
2307            if (top < box.top) {
2308                top = box.top;
2309            }
2310            int bottom = (int) ev.bottom;
2311            if (bottom > box.bottom) {
2312                bottom = box.bottom;
2313            }
2314            if (false) {
2315                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
2316                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
2317                if (DateFormat.is24HourFormat(mContext)) {
2318                    flags |= DateUtils.FORMAT_24HOUR;
2319                }
2320                String timeRange = DateUtils.formatDateRange(mContext,
2321                        ev.startMillis, ev.endMillis, flags);
2322                Log.i("Cal", "left: " + left + " right: " + right + " top: " + top
2323                        + " bottom: " + bottom + " ev: " + timeRange + " " + ev.title);
2324            }
2325            int upDistanceMin = 10000;     // any large number
2326            int downDistanceMin = 10000;   // any large number
2327            int leftDistanceMin = 10000;   // any large number
2328            int rightDistanceMin = 10000;  // any large number
2329            Event upEvent = null;
2330            Event downEvent = null;
2331            Event leftEvent = null;
2332            Event rightEvent = null;
2333
2334            // Pick the starting event closest to the previously selected event,
2335            // if any.  distance1 takes precedence over distance2.
2336            int distance1 = 0;
2337            int distance2 = 0;
2338            if (prevLocation == FROM_ABOVE) {
2339                if (left >= prevCenter) {
2340                    distance1 = left - prevCenter;
2341                } else if (right <= prevCenter) {
2342                    distance1 = prevCenter - right;
2343                }
2344                distance2 = top - prevBottom;
2345            } else if (prevLocation == FROM_BELOW) {
2346                if (left >= prevCenter) {
2347                    distance1 = left - prevCenter;
2348                } else if (right <= prevCenter) {
2349                    distance1 = prevCenter - right;
2350                }
2351                distance2 = prevTop - bottom;
2352            } else if (prevLocation == FROM_LEFT) {
2353                if (bottom <= prevCenter) {
2354                    distance1 = prevCenter - bottom;
2355                } else if (top >= prevCenter) {
2356                    distance1 = top - prevCenter;
2357                }
2358                distance2 = left - prevRight;
2359            } else if (prevLocation == FROM_RIGHT) {
2360                if (bottom <= prevCenter) {
2361                    distance1 = prevCenter - bottom;
2362                } else if (top >= prevCenter) {
2363                    distance1 = top - prevCenter;
2364                }
2365                distance2 = prevLeft - right;
2366            }
2367            if (distance1 < startEventDistance1
2368                    || (distance1 == startEventDistance1 && distance2 < startEventDistance2)) {
2369                startEvent = ev;
2370                startEventDistance1 = distance1;
2371                startEventDistance2 = distance2;
2372            }
2373
2374            // For each neighbor, figure out if it is above or below or left
2375            // or right of me and compute the distance.
2376            for (int jj = 0; jj < len; jj++) {
2377                if (jj == ii) {
2378                    continue;
2379                }
2380                Event neighbor = mSelectedEvents.get(jj);
2381                int neighborLeft = (int) neighbor.left;
2382                int neighborRight = (int) neighbor.right;
2383                if (neighbor.endTime <= startTime) {
2384                    // This neighbor is entirely above me.
2385                    // If we overlap the same column, then compute the distance.
2386                    if (neighborLeft < right && neighborRight > left) {
2387                        int distance = startTime - neighbor.endTime;
2388                        if (distance < upDistanceMin) {
2389                            upDistanceMin = distance;
2390                            upEvent = neighbor;
2391                        } else if (distance == upDistanceMin) {
2392                            int center = (left + right) / 2;
2393                            int currentDistance = 0;
2394                            int currentLeft = (int) upEvent.left;
2395                            int currentRight = (int) upEvent.right;
2396                            if (currentRight <= center) {
2397                                currentDistance = center - currentRight;
2398                            } else if (currentLeft >= center) {
2399                                currentDistance = currentLeft - center;
2400                            }
2401
2402                            int neighborDistance = 0;
2403                            if (neighborRight <= center) {
2404                                neighborDistance = center - neighborRight;
2405                            } else if (neighborLeft >= center) {
2406                                neighborDistance = neighborLeft - center;
2407                            }
2408                            if (neighborDistance < currentDistance) {
2409                                upDistanceMin = distance;
2410                                upEvent = neighbor;
2411                            }
2412                        }
2413                    }
2414                } else if (neighbor.startTime >= endTime) {
2415                    // This neighbor is entirely below me.
2416                    // If we overlap the same column, then compute the distance.
2417                    if (neighborLeft < right && neighborRight > left) {
2418                        int distance = neighbor.startTime - endTime;
2419                        if (distance < downDistanceMin) {
2420                            downDistanceMin = distance;
2421                            downEvent = neighbor;
2422                        } else if (distance == downDistanceMin) {
2423                            int center = (left + right) / 2;
2424                            int currentDistance = 0;
2425                            int currentLeft = (int) downEvent.left;
2426                            int currentRight = (int) downEvent.right;
2427                            if (currentRight <= center) {
2428                                currentDistance = center - currentRight;
2429                            } else if (currentLeft >= center) {
2430                                currentDistance = currentLeft - center;
2431                            }
2432
2433                            int neighborDistance = 0;
2434                            if (neighborRight <= center) {
2435                                neighborDistance = center - neighborRight;
2436                            } else if (neighborLeft >= center) {
2437                                neighborDistance = neighborLeft - center;
2438                            }
2439                            if (neighborDistance < currentDistance) {
2440                                downDistanceMin = distance;
2441                                downEvent = neighbor;
2442                            }
2443                        }
2444                    }
2445                }
2446
2447                if (neighborLeft >= right) {
2448                    // This neighbor is entirely to the right of me.
2449                    // Take the closest neighbor in the y direction.
2450                    int center = (top + bottom) / 2;
2451                    int distance = 0;
2452                    int neighborBottom = (int) neighbor.bottom;
2453                    int neighborTop = (int) neighbor.top;
2454                    if (neighborBottom <= center) {
2455                        distance = center - neighborBottom;
2456                    } else if (neighborTop >= center) {
2457                        distance = neighborTop - center;
2458                    }
2459                    if (distance < rightDistanceMin) {
2460                        rightDistanceMin = distance;
2461                        rightEvent = neighbor;
2462                    } else if (distance == rightDistanceMin) {
2463                        // Pick the closest in the x direction
2464                        int neighborDistance = neighborLeft - right;
2465                        int currentDistance = (int) rightEvent.left - right;
2466                        if (neighborDistance < currentDistance) {
2467                            rightDistanceMin = distance;
2468                            rightEvent = neighbor;
2469                        }
2470                    }
2471                } else if (neighborRight <= left) {
2472                    // This neighbor is entirely to the left of me.
2473                    // Take the closest neighbor in the y direction.
2474                    int center = (top + bottom) / 2;
2475                    int distance = 0;
2476                    int neighborBottom = (int) neighbor.bottom;
2477                    int neighborTop = (int) neighbor.top;
2478                    if (neighborBottom <= center) {
2479                        distance = center - neighborBottom;
2480                    } else if (neighborTop >= center) {
2481                        distance = neighborTop - center;
2482                    }
2483                    if (distance < leftDistanceMin) {
2484                        leftDistanceMin = distance;
2485                        leftEvent = neighbor;
2486                    } else if (distance == leftDistanceMin) {
2487                        // Pick the closest in the x direction
2488                        int neighborDistance = left - neighborRight;
2489                        int currentDistance = left - (int) leftEvent.right;
2490                        if (neighborDistance < currentDistance) {
2491                            leftDistanceMin = distance;
2492                            leftEvent = neighbor;
2493                        }
2494                    }
2495                }
2496            }
2497            ev.nextUp = upEvent;
2498            ev.nextDown = downEvent;
2499            ev.nextLeft = leftEvent;
2500            ev.nextRight = rightEvent;
2501        }
2502        mSelectedEvent = startEvent;
2503    }
2504
2505    private Rect drawEventRect(Event event, Canvas canvas, Paint p, Paint eventTextPaint) {
2506        // Draw the Event Rect
2507        Rect r = mRect;
2508        r.top = (int) event.top + EVENT_RECT_TOP_MARGIN;
2509        r.bottom = (int) event.bottom - EVENT_RECT_BOTTOM_MARGIN;
2510        r.left = (int) event.left + EVENT_RECT_LEFT_MARGIN;
2511        r.right = (int) event.right - EVENT_RECT_RIGHT_MARGIN;
2512
2513        mEventBoxDrawable.setBounds(r);
2514        mEventBoxDrawable.draw(canvas);
2515//        drawEmptyRect(canvas, r, 0xFF00FF00); // for debugging
2516
2517        int eventTextColor = mEventTextColor;
2518        p.setStyle(Style.FILL);
2519
2520        // If this event is selected, then use the selection color
2521        if (mSelectedEvent == event) {
2522            boolean paintIt = false;
2523            int color = 0;
2524            if (mSelectionMode == SELECTION_PRESSED) {
2525                // Also, remember the last selected event that we drew
2526                mPrevSelectedEvent = event;
2527                // box = mBoxPressed;
2528                color = mPressedColor;
2529                eventTextColor = mSelectedEventTextColor;
2530                paintIt = true;
2531            } else if (mSelectionMode == SELECTION_SELECTED) {
2532                // Also, remember the last selected event that we drew
2533                mPrevSelectedEvent = event;
2534                // box = mBoxSelected;
2535                color = mPressedColor;
2536                eventTextColor = mSelectedEventTextColor;
2537                paintIt = true;
2538            } else if (mSelectionMode == SELECTION_LONGPRESS) {
2539                // box = mBoxLongPressed;
2540                color = mPressedColor;
2541                eventTextColor = mSelectedEventTextColor;
2542                paintIt = true;
2543            }
2544
2545            if (paintIt) {
2546                p.setColor(color);
2547                canvas.drawRect(r, p);
2548            }
2549        }
2550
2551        eventTextPaint.setColor(eventTextColor);
2552        // Draw cal color square border
2553        r.top = (int) event.top + CALENDAR_COLOR_SQUARE_V_OFFSET;
2554        r.left = (int) event.left + CALENDAR_COLOR_SQUARE_H_OFFSET;
2555        r.bottom = r.top + CALENDAR_COLOR_SQUARE_SIZE + 1;
2556        r.right = r.left + CALENDAR_COLOR_SQUARE_SIZE + 1;
2557        p.setColor(0xFFFFFFFF);
2558        canvas.drawRect(r, p);
2559
2560        // Draw cal color
2561        r.top++;
2562        r.left++;
2563        r.bottom--;
2564        r.right--;
2565        p.setColor(event.color);
2566        canvas.drawRect(r, p);
2567
2568        boolean declined = (event.selfAttendeeStatus == Attendees.ATTENDEE_STATUS_DECLINED);
2569        if (declined) {
2570            boolean aa = p.isAntiAlias();
2571            if (!aa) {
2572                p.setAntiAlias(true);
2573            }
2574            // Temp behavior
2575            p.setColor(0x88FFFFFF);
2576            canvas.drawLine(r.right, r.top, r.left, r.bottom, p);
2577            if (!aa) {
2578                p.setAntiAlias(false);
2579            }
2580        }
2581
2582        // Setup rect for drawEventText which follows
2583        r.top = (int) event.top + EVENT_RECT_TOP_MARGIN;
2584        r.bottom = (int) event.bottom - EVENT_RECT_BOTTOM_MARGIN;
2585        r.left = (int) event.left + EVENT_RECT_LEFT_MARGIN;
2586        r.right = (int) event.right - EVENT_RECT_RIGHT_MARGIN;
2587        return r;
2588    }
2589
2590    private Pattern drawTextSanitizerFilter = Pattern.compile("[\t\n],");
2591
2592    // Sanitize a string before passing it to drawText or else we get little
2593    // squares. For newlines and tabs before a comma, delete the character.
2594    // Otherwise, just replace them with a space.
2595    private String drawTextSanitizer(String string, int maxEventTextLen) {
2596        Matcher m = drawTextSanitizerFilter.matcher(string);
2597        string = m.replaceAll(",");
2598
2599        int len = string.length();
2600        if (len > maxEventTextLen) {
2601            string = string.substring(0, maxEventTextLen);
2602            len = maxEventTextLen;
2603        }
2604
2605        return string.replace('\n', ' ');
2606    }
2607
2608    private void drawEventText(StaticLayout eventLayout, Rect rect, Canvas canvas, int top,
2609            int bottom) {
2610        // drawEmptyRect(canvas, rect, 0xFFFF00FF); // for debugging
2611
2612        int width = rect.right - rect.left;
2613        int height = rect.bottom - rect.top;
2614
2615        // If the rectangle is too small for text, then return
2616        if (eventLayout == null || width < MIN_CELL_WIDTH_FOR_TEXT) {
2617            return;
2618        }
2619
2620        int totalLineHeight = 0;
2621        int lineCount = eventLayout.getLineCount();
2622        for (int i = 0; i < lineCount; i++) {
2623            int lineBottom = eventLayout.getLineBottom(i);
2624            if (lineBottom <= height) {
2625                totalLineHeight = lineBottom;
2626            } else {
2627                break;
2628            }
2629        }
2630
2631        if (totalLineHeight == 0 || rect.top > bottom || rect.top + totalLineHeight < top) {
2632            return;
2633        }
2634
2635        // Use a StaticLayout to format the string.
2636        canvas.save();
2637        canvas.translate(rect.left, rect.top);
2638        rect.left = 0;
2639        rect.right = width;
2640        rect.top = 0;
2641        rect.bottom = totalLineHeight;
2642
2643        // There's a bug somewhere. If this rect is outside of a previous
2644        // cliprect, this becomes a no-op. What happens is that the text draw
2645        // past the event rect. The current fix is to not draw the staticLayout
2646        // at all if it is completely out of bound.
2647        canvas.clipRect(rect);
2648        eventLayout.draw(canvas);
2649        canvas.restore();
2650    }
2651
2652    // This is to replace p.setStyle(Style.STROKE); canvas.drawRect() since it
2653    // doesn't work well with hardware acceleration
2654    private void drawEmptyRect(Canvas canvas, Rect r, int color) {
2655        int linesIndex = 0;
2656        mLines[linesIndex++] = r.left;
2657        mLines[linesIndex++] = r.top;
2658        mLines[linesIndex++] = r.right;
2659        mLines[linesIndex++] = r.top;
2660
2661        mLines[linesIndex++] = r.left;
2662        mLines[linesIndex++] = r.bottom;
2663        mLines[linesIndex++] = r.right;
2664        mLines[linesIndex++] = r.bottom;
2665
2666        mLines[linesIndex++] = r.left;
2667        mLines[linesIndex++] = r.top;
2668        mLines[linesIndex++] = r.left;
2669        mLines[linesIndex++] = r.bottom;
2670
2671        mLines[linesIndex++] = r.right;
2672        mLines[linesIndex++] = r.top;
2673        mLines[linesIndex++] = r.right;
2674        mLines[linesIndex++] = r.bottom;
2675        mPaint.setColor(color);
2676        canvas.drawLines(mLines, 0, linesIndex, mPaint);
2677    }
2678
2679    private void updateEventDetails() {
2680        if (mSelectedEvent == null || mSelectionMode == SELECTION_HIDDEN
2681                || mSelectionMode == SELECTION_LONGPRESS) {
2682            mPopup.dismiss();
2683            return;
2684        }
2685        if (mLastPopupEventID == mSelectedEvent.id) {
2686            return;
2687        }
2688
2689        mLastPopupEventID = mSelectedEvent.id;
2690
2691        // Remove any outstanding callbacks to dismiss the popup.
2692        getHandler().removeCallbacks(mDismissPopup);
2693
2694        Event event = mSelectedEvent;
2695        TextView titleView = (TextView) mPopupView.findViewById(R.id.event_title);
2696        titleView.setText(event.title);
2697
2698        ImageView imageView = (ImageView) mPopupView.findViewById(R.id.reminder_icon);
2699        imageView.setVisibility(event.hasAlarm ? View.VISIBLE : View.GONE);
2700
2701        imageView = (ImageView) mPopupView.findViewById(R.id.repeat_icon);
2702        imageView.setVisibility(event.isRepeating ? View.VISIBLE : View.GONE);
2703
2704        int flags;
2705        if (event.allDay) {
2706            flags = DateUtils.FORMAT_UTC | DateUtils.FORMAT_SHOW_DATE |
2707                    DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL;
2708        } else {
2709            flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE
2710                    | DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_ABBREV_ALL
2711                    | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
2712        }
2713        if (DateFormat.is24HourFormat(mContext)) {
2714            flags |= DateUtils.FORMAT_24HOUR;
2715        }
2716        String timeRange = Utils.formatDateRange(mContext,
2717                event.startMillis, event.endMillis, flags);
2718        TextView timeView = (TextView) mPopupView.findViewById(R.id.time);
2719        timeView.setText(timeRange);
2720
2721        TextView whereView = (TextView) mPopupView.findViewById(R.id.where);
2722        final boolean empty = TextUtils.isEmpty(event.location);
2723        whereView.setVisibility(empty ? View.GONE : View.VISIBLE);
2724        if (!empty) whereView.setText(event.location);
2725
2726        mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.LEFT, mHoursWidth, 5);
2727        postDelayed(mDismissPopup, POPUP_DISMISS_DELAY);
2728    }
2729
2730    // The following routines are called from the parent activity when certain
2731    // touch events occur.
2732    private void doDown(MotionEvent ev) {
2733        mTouchMode = TOUCH_MODE_DOWN;
2734        mViewStartX = 0;
2735        mOnFlingCalled = false;
2736        getHandler().removeCallbacks(mContinueScroll);
2737    }
2738
2739    private void doSingleTapUp(MotionEvent ev) {
2740        if (!mHandleActionUp) {
2741            return;
2742        }
2743
2744        int x = (int) ev.getX();
2745        int y = (int) ev.getY();
2746        int selectedDay = mSelectionDay;
2747        int selectedHour = mSelectionHour;
2748
2749        boolean validPosition = setSelectionFromPosition(x, y);
2750        if (!validPosition) {
2751            // return if the touch wasn't on an area of concern
2752            return;
2753        }
2754
2755        mSelectionMode = SELECTION_SELECTED;
2756        invalidate();
2757
2758        if (mSelectedEvent != null) {
2759            // If the tap is on an event, launch the "View event" view
2760            mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT, mSelectedEvent.id,
2761                    mSelectedEvent.startMillis, mSelectedEvent.endMillis, (int) ev.getRawX(),
2762                    (int) ev.getRawY());
2763        } else if (selectedDay == mSelectionDay && selectedHour == mSelectionHour) {
2764            // If the tap is on an already selected hour slot, then create a new
2765            // event
2766            mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1,
2767                    getSelectedTimeInMillis(), 0, (int) ev.getRawX(), (int) ev.getRawY());
2768        } else {
2769            Time startTime = new Time(mBaseDate);
2770            startTime.setJulianDay(mSelectionDay);
2771            startTime.hour = mSelectionHour;
2772            startTime.normalize(true /* ignore isDst */);
2773
2774            Time endTime = new Time(startTime);
2775            endTime.hour++;
2776
2777            mController.sendEvent(this, EventType.GO_TO, startTime, endTime, -1, ViewType.CURRENT);
2778        }
2779    }
2780
2781    private void doLongPress(MotionEvent ev) {
2782        // Scale gesture in progress
2783        if (mStartingSpanY != 0) {
2784            return;
2785        }
2786
2787        int x = (int) ev.getX();
2788        int y = (int) ev.getY();
2789
2790        boolean validPosition = setSelectionFromPosition(x, y);
2791        if (!validPosition) {
2792            // return if the touch wasn't on an area of concern
2793            return;
2794        }
2795
2796        mSelectionMode = SELECTION_LONGPRESS;
2797        invalidate();
2798        performLongClick();
2799    }
2800
2801    private void doScroll(MotionEvent e1, MotionEvent e2, float deltaX, float deltaY) {
2802        // Use the distance from the current point to the initial touch instead
2803        // of deltaX and deltaY to avoid accumulating floating-point rounding
2804        // errors.  Also, we don't need floats, we can use ints.
2805        int distanceX = (int) e1.getX() - (int) e2.getX();
2806        int distanceY = (int) e1.getY() - (int) e2.getY();
2807
2808        // If we haven't figured out the predominant scroll direction yet,
2809        // then do it now.
2810        if (mTouchMode == TOUCH_MODE_DOWN) {
2811            int absDistanceX = Math.abs(distanceX);
2812            int absDistanceY = Math.abs(distanceY);
2813            mScrollStartY = mViewStartY;
2814            mPreviousDirection = 0;
2815
2816            // If the x distance is at least twice the y distance, then lock
2817            // the scroll horizontally.  Otherwise scroll vertically.
2818            if (absDistanceX >= 2 * absDistanceY) {
2819                mTouchMode = TOUCH_MODE_HSCROLL;
2820                mViewStartX = distanceX;
2821                initNextView(-mViewStartX);
2822            } else {
2823                mTouchMode = TOUCH_MODE_VSCROLL;
2824            }
2825        } else if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
2826            // We are already scrolling horizontally, so check if we
2827            // changed the direction of scrolling so that the other week
2828            // is now visible.
2829            mViewStartX = distanceX;
2830            if (distanceX != 0) {
2831                int direction = (distanceX > 0) ? 1 : -1;
2832                if (direction != mPreviousDirection) {
2833                    // The user has switched the direction of scrolling
2834                    // so re-init the next view
2835                    initNextView(-mViewStartX);
2836                    mPreviousDirection = direction;
2837                }
2838            }
2839        }
2840
2841        if ((mTouchMode & TOUCH_MODE_VSCROLL) != 0) {
2842            mViewStartY = mScrollStartY + distanceY;
2843            if (mViewStartY < 0) {
2844                mViewStartY = 0;
2845            } else if (mViewStartY > mMaxViewStartY) {
2846                mViewStartY = mMaxViewStartY;
2847            }
2848            computeFirstHour();
2849        }
2850
2851        mScrolling = true;
2852
2853        mSelectionMode = SELECTION_HIDDEN;
2854        invalidate();
2855    }
2856
2857    private void doFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
2858        mTouchMode = TOUCH_MODE_INITIAL_STATE;
2859        mSelectionMode = SELECTION_HIDDEN;
2860        mOnFlingCalled = true;
2861        int deltaX = (int) e2.getX() - (int) e1.getX();
2862        int distanceX = Math.abs(deltaX);
2863        int deltaY = (int) e2.getY() - (int) e1.getY();
2864        int distanceY = Math.abs(deltaY);
2865        if (DEBUG) Log.d(TAG, "doFling: distanceX " + distanceX
2866                         + ", HORIZONTAL_FLING_THRESHOLD " + HORIZONTAL_FLING_THRESHOLD);
2867
2868        if ((distanceX >= HORIZONTAL_FLING_THRESHOLD) && (distanceX > distanceY)) {
2869            // Horizontal fling.
2870            // initNextView(deltaX);
2871            switchViews(deltaX < 0, mViewStartX, mViewWidth);
2872            mViewStartX = 0;
2873            return;
2874        }
2875
2876        // Vertical fling.
2877        mViewStartX = 0;
2878
2879        // Continue scrolling vertically
2880        mContinueScroll.init((int) velocityY / 20);
2881        post(mContinueScroll);
2882    }
2883
2884    private boolean initNextView(int deltaX) {
2885        // Change the view to the previous day or week
2886        DayView view = (DayView) mViewSwitcher.getNextView();
2887        Time date = view.mBaseDate;
2888        date.set(mBaseDate);
2889        boolean switchForward;
2890        if (deltaX > 0) {
2891            date.monthDay -= mNumDays;
2892            view.mSelectionDay = mSelectionDay - mNumDays;
2893            switchForward = false;
2894        } else {
2895            date.monthDay += mNumDays;
2896            view.mSelectionDay = mSelectionDay + mNumDays;
2897            switchForward = true;
2898        }
2899        date.normalize(true /* ignore isDst */);
2900        initView(view);
2901        view.layout(getLeft(), getTop(), getRight(), getBottom());
2902        view.reloadEvents();
2903        return switchForward;
2904    }
2905
2906    // ScaleGestureDetector.OnScaleGestureListener
2907    public boolean onScaleBegin(ScaleGestureDetector detector) {
2908        mHandleActionUp = false;
2909        float gestureCenterInPixels = detector.getFocusY() - DAY_HEADER_HEIGHT - mAllDayHeight;
2910        mGestureCenterHour = (mViewStartY + gestureCenterInPixels) / (mCellHeight + DAY_GAP);
2911
2912        mStartingSpanY = Math.max(MIN_Y_SPAN, Math.abs(detector.getCurrentSpanY()));
2913        mCellHeightBeforeScaleGesture = mCellHeight;
2914
2915        if (DEBUG) {
2916            float ViewStartHour = mViewStartY / (float) (mCellHeight + DAY_GAP);
2917            Log.d(TAG, "mGestureCenterHour:" + mGestureCenterHour + "\tViewStartHour: "
2918                    + ViewStartHour + "\tmViewStartY:" + mViewStartY + "\tmCellHeight:"
2919                    + mCellHeight);
2920        }
2921
2922        return true;
2923    }
2924
2925    // ScaleGestureDetector.OnScaleGestureListener
2926    public boolean onScale(ScaleGestureDetector detector) {
2927        float spanY = Math.abs(detector.getCurrentSpanY());
2928
2929        mCellHeight = (int) (mCellHeightBeforeScaleGesture * spanY / mStartingSpanY);
2930
2931        if (mCellHeight < mMinCellHeight) {
2932            // If mStartingSpanY is too small, even a small increase in the
2933            // gesture can bump the mCellHeight beyond MAX_CELL_HEIGHT
2934            mStartingSpanY = Math.max(MIN_Y_SPAN, spanY);
2935            mCellHeight = mMinCellHeight;
2936            mCellHeightBeforeScaleGesture = mMinCellHeight;
2937        } else if (mCellHeight > MAX_CELL_HEIGHT) {
2938            mStartingSpanY = spanY;
2939            mCellHeight = MAX_CELL_HEIGHT;
2940            mCellHeightBeforeScaleGesture = MAX_CELL_HEIGHT;
2941        }
2942
2943        int gestureCenterInPixels = (int) detector.getFocusY() - DAY_HEADER_HEIGHT - mAllDayHeight;
2944        mViewStartY = (int) (mGestureCenterHour * (mCellHeight + DAY_GAP)) - gestureCenterInPixels;
2945        mMaxViewStartY = HOUR_GAP + 24 * (mCellHeight + HOUR_GAP) - mGridAreaHeight;
2946
2947        if (DEBUG) {
2948            float ViewStartHour = mViewStartY / (float) (mCellHeight + DAY_GAP);
2949            Log.d(TAG, " mGestureCenterHour:" + mGestureCenterHour + "\tViewStartHour: "
2950                    + ViewStartHour + "\tmViewStartY:" + mViewStartY + "\tmCellHeight:"
2951                    + mCellHeight + " SpanY:" + detector.getCurrentSpanY());
2952        }
2953
2954        if (mViewStartY < 0) {
2955            mViewStartY = 0;
2956            mGestureCenterHour = (mViewStartY + gestureCenterInPixels)
2957                    / (float) (mCellHeight + DAY_GAP);
2958        } else if (mViewStartY > mMaxViewStartY) {
2959            mViewStartY = mMaxViewStartY;
2960            mGestureCenterHour = (mViewStartY + gestureCenterInPixels)
2961                    / (float) (mCellHeight + DAY_GAP);
2962        }
2963        computeFirstHour();
2964
2965        mRemeasure = true;
2966        invalidate();
2967        return true;
2968    }
2969
2970    // ScaleGestureDetector.OnScaleGestureListener
2971    public void onScaleEnd(ScaleGestureDetector detector) {
2972        mStartingSpanY = 0;
2973    }
2974
2975    @Override
2976    public boolean onTouchEvent(MotionEvent ev) {
2977        int action = ev.getAction();
2978
2979        if ((mTouchMode & TOUCH_MODE_HSCROLL) == 0) {
2980            mScaleGestureDetector.onTouchEvent(ev);
2981            if (mScaleGestureDetector.isInProgress()) {
2982                return true;
2983            }
2984        }
2985
2986        switch (action) {
2987        case MotionEvent.ACTION_DOWN:
2988            if (DEBUG) Log.e(TAG, "ACTION_DOWN");
2989            mHandleActionUp = true;
2990            mGestureDetector.onTouchEvent(ev);
2991            return true;
2992
2993        case MotionEvent.ACTION_MOVE:
2994            if (DEBUG) Log.e(TAG, "ACTION_MOVE");
2995            mGestureDetector.onTouchEvent(ev);
2996            return true;
2997
2998        case MotionEvent.ACTION_UP:
2999            if (DEBUG) Log.e(TAG, "ACTION_UP " + mHandleActionUp);
3000            mGestureDetector.onTouchEvent(ev);
3001            if (!mHandleActionUp) {
3002                mHandleActionUp = true;
3003                return true;
3004            }
3005            if (mOnFlingCalled) {
3006                return true;
3007            }
3008            if ((mTouchMode & TOUCH_MODE_HSCROLL) != 0) {
3009                mTouchMode = TOUCH_MODE_INITIAL_STATE;
3010                if (Math.abs(mViewStartX) > mHorizontalSnapBackThreshold) {
3011                    // The user has gone beyond the threshold so switch views
3012                    if (DEBUG) Log.d(TAG, "- horizontal scroll: switch views");
3013                    switchViews(mViewStartX > 0, mViewStartX, mViewWidth);
3014                    mViewStartX = 0;
3015                    return true;
3016                } else {
3017                    // Not beyond the threshold so invalidate which will cause
3018                    // the view to snap back.  Also call recalc() to ensure
3019                    // that we have the correct starting date and title.
3020                    if (DEBUG) Log.d(TAG, "- horizontal scroll: snap back");
3021                    recalc();
3022                    invalidate();
3023                    mViewStartX = 0;
3024                }
3025            }
3026
3027            // If we were scrolling, then reset the selected hour so that it
3028            // is visible.
3029            if (mScrolling) {
3030                mScrolling = false;
3031                resetSelectedHour();
3032                invalidate();
3033            }
3034            return true;
3035
3036        // This case isn't expected to happen.
3037        case MotionEvent.ACTION_CANCEL:
3038            if (DEBUG) Log.e(TAG, "ACTION_CANCEL");
3039            mGestureDetector.onTouchEvent(ev);
3040            mScrolling = false;
3041            resetSelectedHour();
3042            return true;
3043
3044        default:
3045            if (DEBUG) Log.e(TAG, "Not MotionEvent");
3046            if (mGestureDetector.onTouchEvent(ev)) {
3047                return true;
3048            }
3049            return super.onTouchEvent(ev);
3050        }
3051    }
3052
3053    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
3054        MenuItem item;
3055
3056        // If the trackball is held down, then the context menu pops up and
3057        // we never get onKeyUp() for the long-press.  So check for it here
3058        // and change the selection to the long-press state.
3059        if (mSelectionMode != SELECTION_LONGPRESS) {
3060            mSelectionMode = SELECTION_LONGPRESS;
3061            invalidate();
3062        }
3063
3064        final long startMillis = getSelectedTimeInMillis();
3065        int flags = DateUtils.FORMAT_SHOW_TIME
3066                | DateUtils.FORMAT_CAP_NOON_MIDNIGHT
3067                | DateUtils.FORMAT_SHOW_WEEKDAY;
3068        final String title = Utils.formatDateRange(mContext, startMillis, startMillis, flags);
3069        menu.setHeaderTitle(title);
3070
3071        int numSelectedEvents = mSelectedEvents.size();
3072        if (mNumDays == 1) {
3073            // Day view.
3074
3075            // If there is a selected event, then allow it to be viewed and
3076            // edited.
3077            if (numSelectedEvents >= 1) {
3078                item = menu.add(0, MENU_EVENT_VIEW, 0, R.string.event_view);
3079                item.setOnMenuItemClickListener(mContextMenuHandler);
3080                item.setIcon(android.R.drawable.ic_menu_info_details);
3081
3082                int accessLevel = getEventAccessLevel(mContext, mSelectedEvent);
3083                if (accessLevel == ACCESS_LEVEL_EDIT) {
3084                    item = menu.add(0, MENU_EVENT_EDIT, 0, R.string.event_edit);
3085                    item.setOnMenuItemClickListener(mContextMenuHandler);
3086                    item.setIcon(android.R.drawable.ic_menu_edit);
3087                    item.setAlphabeticShortcut('e');
3088                }
3089
3090                if (accessLevel >= ACCESS_LEVEL_DELETE) {
3091                    item = menu.add(0, MENU_EVENT_DELETE, 0, R.string.event_delete);
3092                    item.setOnMenuItemClickListener(mContextMenuHandler);
3093                    item.setIcon(android.R.drawable.ic_menu_delete);
3094                }
3095
3096                item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create);
3097                item.setOnMenuItemClickListener(mContextMenuHandler);
3098                item.setIcon(android.R.drawable.ic_menu_add);
3099                item.setAlphabeticShortcut('n');
3100            } else {
3101                // Otherwise, if the user long-pressed on a blank hour, allow
3102                // them to create an event.  They can also do this by tapping.
3103                item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create);
3104                item.setOnMenuItemClickListener(mContextMenuHandler);
3105                item.setIcon(android.R.drawable.ic_menu_add);
3106                item.setAlphabeticShortcut('n');
3107            }
3108        } else {
3109            // Week view.
3110
3111            // If there is a selected event, then allow it to be viewed and
3112            // edited.
3113            if (numSelectedEvents >= 1) {
3114                item = menu.add(0, MENU_EVENT_VIEW, 0, R.string.event_view);
3115                item.setOnMenuItemClickListener(mContextMenuHandler);
3116                item.setIcon(android.R.drawable.ic_menu_info_details);
3117
3118                int accessLevel = getEventAccessLevel(mContext, mSelectedEvent);
3119                if (accessLevel == ACCESS_LEVEL_EDIT) {
3120                    item = menu.add(0, MENU_EVENT_EDIT, 0, R.string.event_edit);
3121                    item.setOnMenuItemClickListener(mContextMenuHandler);
3122                    item.setIcon(android.R.drawable.ic_menu_edit);
3123                    item.setAlphabeticShortcut('e');
3124                }
3125
3126                if (accessLevel >= ACCESS_LEVEL_DELETE) {
3127                    item = menu.add(0, MENU_EVENT_DELETE, 0, R.string.event_delete);
3128                    item.setOnMenuItemClickListener(mContextMenuHandler);
3129                    item.setIcon(android.R.drawable.ic_menu_delete);
3130                }
3131            }
3132
3133            item = menu.add(0, MENU_EVENT_CREATE, 0, R.string.event_create);
3134            item.setOnMenuItemClickListener(mContextMenuHandler);
3135            item.setIcon(android.R.drawable.ic_menu_add);
3136            item.setAlphabeticShortcut('n');
3137
3138            item = menu.add(0, MENU_DAY, 0, R.string.show_day_view);
3139            item.setOnMenuItemClickListener(mContextMenuHandler);
3140            item.setIcon(android.R.drawable.ic_menu_day);
3141            item.setAlphabeticShortcut('d');
3142        }
3143
3144        mPopup.dismiss();
3145    }
3146
3147    private class ContextMenuHandler implements MenuItem.OnMenuItemClickListener {
3148        public boolean onMenuItemClick(MenuItem item) {
3149            switch (item.getItemId()) {
3150                case MENU_EVENT_VIEW: {
3151                    if (mSelectedEvent != null) {
3152                        mController.sendEventRelatedEvent(this, EventType.VIEW_EVENT_DETAILS,
3153                                mSelectedEvent.id, mSelectedEvent.startMillis,
3154                                mSelectedEvent.endMillis, 0, 0);
3155                    }
3156                    break;
3157                }
3158                case MENU_EVENT_EDIT: {
3159                    if (mSelectedEvent != null) {
3160                        mController.sendEventRelatedEvent(this, EventType.EDIT_EVENT,
3161                                mSelectedEvent.id, mSelectedEvent.startMillis,
3162                                mSelectedEvent.endMillis, 0, 0);
3163                    }
3164                    break;
3165                }
3166                case MENU_DAY: {
3167                    mController.sendEvent(this, EventType.GO_TO, getSelectedTime(), null, -1,
3168                            ViewType.DAY);
3169                    break;
3170                }
3171                case MENU_AGENDA: {
3172                    mController.sendEvent(this, EventType.GO_TO, getSelectedTime(), null, -1,
3173                            ViewType.AGENDA);
3174                    break;
3175                }
3176                case MENU_EVENT_CREATE: {
3177                    long startMillis = getSelectedTimeInMillis();
3178                    long endMillis = startMillis + DateUtils.HOUR_IN_MILLIS;
3179                    mController.sendEventRelatedEvent(this, EventType.CREATE_EVENT, -1,
3180                            startMillis, endMillis, 0, 0);
3181                    break;
3182                }
3183                case MENU_EVENT_DELETE: {
3184                    if (mSelectedEvent != null) {
3185                        Event selectedEvent = mSelectedEvent;
3186                        long begin = selectedEvent.startMillis;
3187                        long end = selectedEvent.endMillis;
3188                        long id = selectedEvent.id;
3189                        mController.sendEventRelatedEvent(this, EventType.DELETE_EVENT, id, begin,
3190                                end, 0, 0);
3191                    }
3192                    break;
3193                }
3194                default: {
3195                    return false;
3196                }
3197            }
3198            return true;
3199        }
3200    }
3201
3202    private static int getEventAccessLevel(Context context, Event e) {
3203        ContentResolver cr = context.getContentResolver();
3204
3205        int visibility = Calendars.NO_ACCESS;
3206
3207        // Get the calendar id for this event
3208        Cursor cursor = cr.query(ContentUris.withAppendedId(Events.CONTENT_URI, e.id),
3209                new String[] { Events.CALENDAR_ID },
3210                null /* selection */,
3211                null /* selectionArgs */,
3212                null /* sort */);
3213
3214        if (cursor == null) {
3215            return ACCESS_LEVEL_NONE;
3216        }
3217
3218        if (cursor.getCount() == 0) {
3219            cursor.close();
3220            return ACCESS_LEVEL_NONE;
3221        }
3222
3223        cursor.moveToFirst();
3224        long calId = cursor.getLong(0);
3225        cursor.close();
3226
3227        Uri uri = Calendars.CONTENT_URI;
3228        String where = String.format(CALENDARS_WHERE, calId);
3229        cursor = cr.query(uri, CALENDARS_PROJECTION, where, null, null);
3230
3231        String calendarOwnerAccount = null;
3232        if (cursor != null) {
3233            cursor.moveToFirst();
3234            visibility = cursor.getInt(CALENDARS_INDEX_ACCESS_LEVEL);
3235            calendarOwnerAccount = cursor.getString(CALENDARS_INDEX_OWNER_ACCOUNT);
3236            cursor.close();
3237        }
3238
3239        if (visibility < Calendars.CONTRIBUTOR_ACCESS) {
3240            return ACCESS_LEVEL_NONE;
3241        }
3242
3243        if (e.guestsCanModify) {
3244            return ACCESS_LEVEL_EDIT;
3245        }
3246
3247        if (!TextUtils.isEmpty(calendarOwnerAccount) &&
3248                calendarOwnerAccount.equalsIgnoreCase(e.organizer)) {
3249            return ACCESS_LEVEL_EDIT;
3250        }
3251
3252        return ACCESS_LEVEL_DELETE;
3253    }
3254
3255    /**
3256     * Sets mSelectionDay and mSelectionHour based on the (x,y) touch position.
3257     * If the touch position is not within the displayed grid, then this
3258     * method returns false.
3259     *
3260     * @param x the x position of the touch
3261     * @param y the y position of the touch
3262     * @return true if the touch position is valid
3263     */
3264    private boolean setSelectionFromPosition(int x, int y) {
3265        if (x < mHoursWidth) {
3266            return false;
3267        }
3268
3269        int day = (x - mHoursWidth) / (mCellWidth + DAY_GAP);
3270        if (day >= mNumDays) {
3271            day = mNumDays - 1;
3272        }
3273        day += mFirstJulianDay;
3274        int hour;
3275        if (y < mFirstCell + mFirstHourOffset) {
3276            mSelectionAllDay = true;
3277        } else {
3278            hour = (y - mFirstCell - mFirstHourOffset) / (mCellHeight + HOUR_GAP);
3279            hour += mFirstHour;
3280            mSelectionHour = hour;
3281            mSelectionAllDay = false;
3282        }
3283        mSelectionDay = day;
3284        findSelectedEvent(x, y);
3285//        Log.i("Cal", "setSelectionFromPosition( " + x + ", " + y + " ) day: " + day
3286//                + " hour: " + hour
3287//                + " mFirstCell: " + mFirstCell + " mFirstHourOffset: " + mFirstHourOffset);
3288//        if (mSelectedEvent != null) {
3289//            Log.i("Cal", "  num events: " + mSelectedEvents.size() + " event: " + mSelectedEvent.title);
3290//            for (Event ev : mSelectedEvents) {
3291//                int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL
3292//                        | DateUtils.FORMAT_CAP_NOON_MIDNIGHT;
3293//                String timeRange = formatDateRange(mContext,
3294//                        ev.startMillis, ev.endMillis, flags);
3295//
3296//                Log.i("Cal", "  " + timeRange + " " + ev.title);
3297//            }
3298//        }
3299        return true;
3300    }
3301
3302    private void findSelectedEvent(int x, int y) {
3303        int date = mSelectionDay;
3304        int cellWidth = mCellWidth;
3305        final ArrayList<Event> events = mEvents;
3306        int numEvents = events.size();
3307        int left = mHoursWidth + (mSelectionDay - mFirstJulianDay) * (cellWidth + DAY_GAP);
3308        int top = 0;
3309        mSelectedEvent = null;
3310
3311        mSelectedEvents.clear();
3312        if (mSelectionAllDay) {
3313            float yDistance;
3314            float minYdistance = 10000.0f;  // any large number
3315            Event closestEvent = null;
3316            float drawHeight = mAllDayHeight;
3317            int yOffset = DAY_HEADER_HEIGHT + ALLDAY_TOP_MARGIN;
3318            for (int i = 0; i < numEvents; i++) {
3319                Event event = events.get(i);
3320                if (!event.allDay) {
3321                    continue;
3322                }
3323
3324                if (event.startDay <= mSelectionDay && event.endDay >= mSelectionDay) {
3325                    float numRectangles = event.getMaxColumns();
3326                    float height = drawHeight / numRectangles;
3327                    if (height > MAX_HEIGHT_OF_ONE_ALLDAY_EVENT) {
3328                        height = MAX_HEIGHT_OF_ONE_ALLDAY_EVENT;
3329                    }
3330                    float eventTop = yOffset + height * event.getColumn();
3331                    float eventBottom = eventTop + height;
3332                    if (eventTop < y && eventBottom > y) {
3333                        // If the touch is inside the event rectangle, then
3334                        // add the event.
3335                        mSelectedEvents.add(event);
3336                        closestEvent = event;
3337                        break;
3338                    } else {
3339                        // Find the closest event
3340                        if (eventTop >= y) {
3341                            yDistance = eventTop - y;
3342                        } else {
3343                            yDistance = y - eventBottom;
3344                        }
3345                        if (yDistance < minYdistance) {
3346                            minYdistance = yDistance;
3347                            closestEvent = event;
3348                        }
3349                    }
3350                }
3351            }
3352            mSelectedEvent = closestEvent;
3353            return;
3354        }
3355
3356        // Adjust y for the scrollable bitmap
3357        y += mViewStartY - mFirstCell;
3358
3359        // Use a region around (x,y) for the selection region
3360        Rect region = mRect;
3361        region.left = x - 10;
3362        region.right = x + 10;
3363        region.top = y - 10;
3364        region.bottom = y + 10;
3365
3366        EventGeometry geometry = mEventGeometry;
3367
3368        for (int i = 0; i < numEvents; i++) {
3369            Event event = events.get(i);
3370            // Compute the event rectangle.
3371            if (!geometry.computeEventRect(date, left, top, cellWidth, event)) {
3372                continue;
3373            }
3374
3375            // If the event intersects the selection region, then add it to
3376            // mSelectedEvents.
3377            if (geometry.eventIntersectsSelection(event, region)) {
3378                mSelectedEvents.add(event);
3379            }
3380        }
3381
3382        // If there are any events in the selected region, then assign the
3383        // closest one to mSelectedEvent.
3384        if (mSelectedEvents.size() > 0) {
3385            int len = mSelectedEvents.size();
3386            Event closestEvent = null;
3387            float minDist = mViewWidth + mViewHeight;  // some large distance
3388            for (int index = 0; index < len; index++) {
3389                Event ev = mSelectedEvents.get(index);
3390                float dist = geometry.pointToEvent(x, y, ev);
3391                if (dist < minDist) {
3392                    minDist = dist;
3393                    closestEvent = ev;
3394                }
3395            }
3396            mSelectedEvent = closestEvent;
3397
3398            // Keep the selected hour and day consistent with the selected
3399            // event.  They could be different if we touched on an empty hour
3400            // slot very close to an event in the previous hour slot.  In
3401            // that case we will select the nearby event.
3402            int startDay = mSelectedEvent.startDay;
3403            int endDay = mSelectedEvent.endDay;
3404            if (mSelectionDay < startDay) {
3405                mSelectionDay = startDay;
3406            } else if (mSelectionDay > endDay) {
3407                mSelectionDay = endDay;
3408            }
3409
3410            int startHour = mSelectedEvent.startTime / 60;
3411            int endHour;
3412            if (mSelectedEvent.startTime < mSelectedEvent.endTime) {
3413                endHour = (mSelectedEvent.endTime - 1) / 60;
3414            } else {
3415                endHour = mSelectedEvent.endTime / 60;
3416            }
3417
3418            if (mSelectionHour < startHour) {
3419                mSelectionHour = startHour;
3420            } else if (mSelectionHour > endHour) {
3421                mSelectionHour = endHour;
3422            }
3423        }
3424    }
3425
3426    // Encapsulates the code to continue the scrolling after the
3427    // finger is lifted.  Instead of stopping the scroll immediately,
3428    // the scroll continues to "free spin" and gradually slows down.
3429    private class ContinueScroll implements Runnable {
3430        int mSignDeltaY;
3431        int mAbsDeltaY;
3432        float mFloatDeltaY;
3433        long mFreeSpinTime;
3434        private static final float FRICTION_COEF = 0.7F;
3435        private static final long FREE_SPIN_MILLIS = 180;
3436        private static final int MAX_DELTA = 60;
3437        private static final int SCROLL_REPEAT_INTERVAL = 30;
3438
3439        public void init(int deltaY) {
3440            mSignDeltaY = 0;
3441            if (deltaY > 0) {
3442                mSignDeltaY = 1;
3443            } else if (deltaY < 0) {
3444                mSignDeltaY = -1;
3445            }
3446            mAbsDeltaY = Math.abs(deltaY);
3447
3448            // Limit the maximum speed
3449            if (mAbsDeltaY > MAX_DELTA) {
3450                mAbsDeltaY = MAX_DELTA;
3451            }
3452            mFloatDeltaY = mAbsDeltaY;
3453            mFreeSpinTime = System.currentTimeMillis() + FREE_SPIN_MILLIS;
3454//            Log.i("Cal", "init scroll: mAbsDeltaY: " + mAbsDeltaY
3455//                    + " mViewStartY: " + mViewStartY);
3456        }
3457
3458        public void run() {
3459            long time = System.currentTimeMillis();
3460
3461            // Start out with a frictionless "free spin"
3462            if (time > mFreeSpinTime) {
3463                // If the delta is small, then apply a fixed deceleration.
3464                // Otherwise
3465                if (mAbsDeltaY <= 10) {
3466                    mAbsDeltaY -= 2;
3467                } else {
3468                    mFloatDeltaY *= FRICTION_COEF;
3469                    mAbsDeltaY = (int) mFloatDeltaY;
3470                }
3471
3472                if (mAbsDeltaY < 0) {
3473                    mAbsDeltaY = 0;
3474                }
3475            }
3476
3477            if (mSignDeltaY == 1) {
3478                mViewStartY -= mAbsDeltaY;
3479            } else {
3480                mViewStartY += mAbsDeltaY;
3481            }
3482//            Log.i("Cal", "  scroll: mAbsDeltaY: " + mAbsDeltaY
3483//                    + " mViewStartY: " + mViewStartY);
3484
3485            if (mViewStartY < 0) {
3486                mViewStartY = 0;
3487                mAbsDeltaY = 0;
3488            } else if (mViewStartY > mMaxViewStartY) {
3489                mViewStartY = mMaxViewStartY;
3490                mAbsDeltaY = 0;
3491            }
3492
3493            computeFirstHour();
3494
3495            if (mAbsDeltaY > 0) {
3496                postDelayed(this, SCROLL_REPEAT_INTERVAL);
3497            } else {
3498                // Done scrolling.
3499                mScrolling = false;
3500                resetSelectedHour();
3501            }
3502
3503            invalidate();
3504        }
3505    }
3506
3507    /**
3508     * Cleanup the pop-up and timers.
3509     */
3510    public void cleanup() {
3511        // Protect against null-pointer exceptions
3512        if (mPopup != null) {
3513            mPopup.dismiss();
3514        }
3515        mLastPopupEventID = INVALID_EVENT_ID;
3516        Handler handler = getHandler();
3517        if (handler != null) {
3518            handler.removeCallbacks(mDismissPopup);
3519            handler.removeCallbacks(mUpdateCurrentTime);
3520        }
3521
3522        Utils.setSharedPreference(mContext, GeneralPreferences.KEY_DEFAULT_CELL_HEIGHT,
3523            mCellHeight);
3524
3525        // Turn off redraw
3526        mRemeasure = false;
3527    }
3528
3529    /**
3530     * Restart the update timer
3531     */
3532    public void restartCurrentTimeUpdates() {
3533        post(mUpdateCurrentTime);
3534    }
3535
3536    @Override protected void onDetachedFromWindow() {
3537        cleanup();
3538        super.onDetachedFromWindow();
3539    }
3540
3541    class DismissPopup implements Runnable {
3542        public void run() {
3543            // Protect against null-pointer exceptions
3544            if (mPopup != null) {
3545                mPopup.dismiss();
3546            }
3547        }
3548    }
3549
3550    class UpdateCurrentTime implements Runnable {
3551        public void run() {
3552            long currentTime = System.currentTimeMillis();
3553            mCurrentTime.set(currentTime);
3554            //% causes update to occur on 5 minute marks (11:10, 11:15, 11:20, etc.)
3555            postDelayed(mUpdateCurrentTime,
3556                    UPDATE_CURRENT_TIME_DELAY - (currentTime % UPDATE_CURRENT_TIME_DELAY));
3557            mTodayJulianDay = Time.getJulianDay(currentTime, mCurrentTime.gmtoff);
3558            invalidate();
3559        }
3560    }
3561
3562    class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
3563        @Override
3564        public boolean onSingleTapUp(MotionEvent ev) {
3565            DayView.this.doSingleTapUp(ev);
3566            return true;
3567        }
3568
3569        @Override
3570        public void onLongPress(MotionEvent ev) {
3571            DayView.this.doLongPress(ev);
3572        }
3573
3574        @Override
3575        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
3576            DayView.this.doScroll(e1, e2, distanceX, distanceY);
3577            return true;
3578        }
3579
3580        @Override
3581        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
3582            DayView.this.doFling(e1, e2, velocityX, velocityY);
3583            return true;
3584        }
3585
3586        @Override
3587        public boolean onDown(MotionEvent ev) {
3588            DayView.this.doDown(ev);
3589            return true;
3590        }
3591    }
3592}
3593