Home | History | Annotate | Download | only in month
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.calendar.month;
     18 
     19 import com.android.calendar.R;
     20 import com.android.calendar.Utils;
     21 
     22 import android.app.Service;
     23 import android.content.Context;
     24 import android.content.res.Resources;
     25 import android.graphics.Canvas;
     26 import android.graphics.Paint;
     27 import android.graphics.Paint.Align;
     28 import android.graphics.Paint.Style;
     29 import android.graphics.Rect;
     30 import android.graphics.drawable.Drawable;
     31 import android.text.format.DateUtils;
     32 import android.text.format.Time;
     33 import android.view.MotionEvent;
     34 import android.view.View;
     35 import android.view.accessibility.AccessibilityEvent;
     36 import android.view.accessibility.AccessibilityManager;
     37 
     38 import java.security.InvalidParameterException;
     39 import java.util.HashMap;
     40 
     41 /**
     42  * <p>
     43  * This is a dynamic view for drawing a single week. It can be configured to
     44  * display the week number, start the week on a given day, or show a reduced
     45  * number of days. It is intended for use as a single view within a ListView.
     46  * See {@link SimpleWeeksAdapter} for usage.
     47  * </p>
     48  */
     49 public class SimpleWeekView extends View {
     50     private static final String TAG = "MonthView";
     51 
     52     /**
     53      * These params can be passed into the view to control how it appears.
     54      * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
     55      * values are unlikely to fit most layouts correctly.
     56      */
     57     /**
     58      * This sets the height of this week in pixels
     59      */
     60     public static final String VIEW_PARAMS_HEIGHT = "height";
     61     /**
     62      * This specifies the position (or weeks since the epoch) of this week,
     63      * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
     64      */
     65     public static final String VIEW_PARAMS_WEEK = "week";
     66     /**
     67      * This sets one of the days in this view as selected {@link Time#SUNDAY}
     68      * through {@link Time#SATURDAY}.
     69      */
     70     public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";
     71     /**
     72      * Which day the week should start on. {@link Time#SUNDAY} through
     73      * {@link Time#SATURDAY}.
     74      */
     75     public static final String VIEW_PARAMS_WEEK_START = "week_start";
     76     /**
     77      * How many days to display at a time. Days will be displayed starting with
     78      * {@link #mWeekStart}.
     79      */
     80     public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
     81     /**
     82      * Which month is currently in focus, as defined by {@link Time#month}
     83      * [0-11].
     84      */
     85     public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
     86     /**
     87      * If this month should display week numbers. false if 0, true otherwise.
     88      */
     89     public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";
     90 
     91     protected static int DEFAULT_HEIGHT = 32;
     92     protected static int MIN_HEIGHT = 10;
     93     protected static final int DEFAULT_SELECTED_DAY = -1;
     94     protected static final int DEFAULT_WEEK_START = Time.SUNDAY;
     95     protected static final int DEFAULT_NUM_DAYS = 7;
     96     protected static final int DEFAULT_SHOW_WK_NUM = 0;
     97     protected static final int DEFAULT_FOCUS_MONTH = -1;
     98 
     99     protected static int DAY_SEPARATOR_WIDTH = 1;
    100 
    101     protected static int MINI_DAY_NUMBER_TEXT_SIZE = 14;
    102     protected static int MINI_WK_NUMBER_TEXT_SIZE = 12;
    103     protected static int MINI_TODAY_NUMBER_TEXT_SIZE = 18;
    104     protected static int MINI_TODAY_OUTLINE_WIDTH = 2;
    105     protected static int WEEK_NUM_MARGIN_BOTTOM = 4;
    106 
    107     // used for scaling to the device density
    108     protected static float mScale = 0;
    109 
    110     // affects the padding on the sides of this view
    111     protected int mPadding = 0;
    112 
    113     protected Rect r = new Rect();
    114     protected Paint p = new Paint();
    115     protected Paint mMonthNumPaint;
    116     protected Drawable mSelectedDayLine;
    117 
    118     // Cache the number strings so we don't have to recompute them each time
    119     protected String[] mDayNumbers;
    120     // Quick lookup for checking which days are in the focus month
    121     protected boolean[] mFocusDay;
    122     // Quick lookup for checking which days are in an odd month (to set a different background)
    123     protected boolean[] mOddMonth;
    124     // The Julian day of the first day displayed by this item
    125     protected int mFirstJulianDay = -1;
    126     // The month of the first day in this week
    127     protected int mFirstMonth = -1;
    128     // The month of the last day in this week
    129     protected int mLastMonth = -1;
    130     // The position of this week, equivalent to weeks since the week of Jan 1st,
    131     // 1970
    132     protected int mWeek = -1;
    133     // Quick reference to the width of this view, matches parent
    134     protected int mWidth;
    135     // The height this view should draw at in pixels, set by height param
    136     protected int mHeight = DEFAULT_HEIGHT;
    137     // Whether the week number should be shown
    138     protected boolean mShowWeekNum = false;
    139     // If this view contains the selected day
    140     protected boolean mHasSelectedDay = false;
    141     // If this view contains the today
    142     protected boolean mHasToday = false;
    143     // Which day is selected [0-6] or -1 if no day is selected
    144     protected int mSelectedDay = DEFAULT_SELECTED_DAY;
    145     // Which day is today [0-6] or -1 if no day is today
    146     protected int mToday = DEFAULT_SELECTED_DAY;
    147     // Which day of the week to start on [0-6]
    148     protected int mWeekStart = DEFAULT_WEEK_START;
    149     // How many days to display
    150     protected int mNumDays = DEFAULT_NUM_DAYS;
    151     // The number of days + a spot for week number if it is displayed
    152     protected int mNumCells = mNumDays;
    153     // The left edge of the selected day
    154     protected int mSelectedLeft = -1;
    155     // The right edge of the selected day
    156     protected int mSelectedRight = -1;
    157     // The timezone to display times/dates in (used for determining when Today
    158     // is)
    159     protected String mTimeZone = Time.getCurrentTimezone();
    160 
    161     protected int mBGColor;
    162     protected int mSelectedWeekBGColor;
    163     protected int mFocusMonthColor;
    164     protected int mOtherMonthColor;
    165     protected int mDaySeparatorColor;
    166     protected int mTodayOutlineColor;
    167     protected int mWeekNumColor;
    168 
    169     public SimpleWeekView(Context context) {
    170         super(context);
    171 
    172         Resources res = context.getResources();
    173 
    174         mBGColor = res.getColor(R.color.month_bgcolor);
    175         mSelectedWeekBGColor = res.getColor(R.color.month_selected_week_bgcolor);
    176         mFocusMonthColor = res.getColor(R.color.month_mini_day_number);
    177         mOtherMonthColor = res.getColor(R.color.month_other_month_day_number);
    178         mDaySeparatorColor = res.getColor(R.color.month_grid_lines);
    179         mTodayOutlineColor = res.getColor(R.color.mini_month_today_outline_color);
    180         mWeekNumColor = res.getColor(R.color.month_week_num_color);
    181         mSelectedDayLine = res.getDrawable(R.drawable.dayline_minical_holo_light);
    182 
    183         if (mScale == 0) {
    184             mScale = context.getResources().getDisplayMetrics().density;
    185             if (mScale != 1) {
    186                 DEFAULT_HEIGHT *= mScale;
    187                 MIN_HEIGHT *= mScale;
    188                 MINI_DAY_NUMBER_TEXT_SIZE *= mScale;
    189                 MINI_TODAY_NUMBER_TEXT_SIZE *= mScale;
    190                 MINI_TODAY_OUTLINE_WIDTH *= mScale;
    191                 WEEK_NUM_MARGIN_BOTTOM *= mScale;
    192                 DAY_SEPARATOR_WIDTH *= mScale;
    193                 MINI_WK_NUMBER_TEXT_SIZE *= mScale;
    194             }
    195         }
    196 
    197         // Sets up any standard paints that will be used
    198         initView();
    199     }
    200 
    201     /**
    202      * Sets all the parameters for displaying this week. The only required
    203      * parameter is the week number. Other parameters have a default value and
    204      * will only update if a new value is included, except for focus month,
    205      * which will always default to no focus month if no value is passed in. See
    206      * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
    207      *
    208      * @param params A map of the new parameters, see
    209      *            {@link #VIEW_PARAMS_HEIGHT}
    210      * @param tz The time zone this view should reference times in
    211      */
    212     public void setWeekParams(HashMap<String, Integer> params, String tz) {
    213         if (!params.containsKey(VIEW_PARAMS_WEEK)) {
    214             throw new InvalidParameterException("You must specify the week number for this view");
    215         }
    216         setTag(params);
    217         mTimeZone = tz;
    218         // We keep the current value for any params not present
    219         if (params.containsKey(VIEW_PARAMS_HEIGHT)) {
    220             mHeight = params.get(VIEW_PARAMS_HEIGHT);
    221             if (mHeight < MIN_HEIGHT) {
    222                 mHeight = MIN_HEIGHT;
    223             }
    224         }
    225         if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) {
    226             mSelectedDay = params.get(VIEW_PARAMS_SELECTED_DAY);
    227         }
    228         mHasSelectedDay = mSelectedDay != -1;
    229         if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) {
    230             mNumDays = params.get(VIEW_PARAMS_NUM_DAYS);
    231         }
    232         if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) {
    233             if (params.get(VIEW_PARAMS_SHOW_WK_NUM) != 0) {
    234                 mShowWeekNum = true;
    235             } else {
    236                 mShowWeekNum = false;
    237             }
    238         }
    239         mNumCells = mShowWeekNum ? mNumDays + 1 : mNumDays;
    240 
    241         // Allocate space for caching the day numbers and focus values
    242         mDayNumbers = new String[mNumCells];
    243         mFocusDay = new boolean[mNumCells];
    244         mOddMonth = new boolean[mNumCells];
    245         mWeek = params.get(VIEW_PARAMS_WEEK);
    246         int julianMonday = Utils.getJulianMondayFromWeeksSinceEpoch(mWeek);
    247         Time time = new Time(tz);
    248         time.setJulianDay(julianMonday);
    249 
    250         // If we're showing the week number calculate it based on Monday
    251         int i = 0;
    252         if (mShowWeekNum) {
    253             mDayNumbers[0] = Integer.toString(time.getWeekNumber());
    254             i++;
    255         }
    256 
    257         if (params.containsKey(VIEW_PARAMS_WEEK_START)) {
    258             mWeekStart = params.get(VIEW_PARAMS_WEEK_START);
    259         }
    260 
    261         // Now adjust our starting day based on the start day of the week
    262         // If the week is set to start on a Saturday the first week will be
    263         // Dec 27th 1969 -Jan 2nd, 1970
    264         if (time.weekDay != mWeekStart) {
    265             int diff = time.weekDay - mWeekStart;
    266             if (diff < 0) {
    267                 diff += 7;
    268             }
    269             time.monthDay -= diff;
    270             time.normalize(true);
    271         }
    272 
    273         mFirstJulianDay = Time.getJulianDay(time.toMillis(true), time.gmtoff);
    274         mFirstMonth = time.month;
    275 
    276         // Figure out what day today is
    277         Time today = new Time(tz);
    278         today.setToNow();
    279         mHasToday = false;
    280         mToday = -1;
    281 
    282         int focusMonth = params.containsKey(VIEW_PARAMS_FOCUS_MONTH) ? params.get(
    283                 VIEW_PARAMS_FOCUS_MONTH)
    284                 : DEFAULT_FOCUS_MONTH;
    285 
    286         for (; i < mNumCells; i++) {
    287             if (time.monthDay == 1) {
    288                 mFirstMonth = time.month;
    289             }
    290             mOddMonth [i] = (time.month %2) == 1;
    291             if (time.month == focusMonth) {
    292                 mFocusDay[i] = true;
    293             } else {
    294                 mFocusDay[i] = false;
    295             }
    296             if (time.year == today.year && time.yearDay == today.yearDay) {
    297                 mHasToday = true;
    298                 mToday = i;
    299             }
    300             mDayNumbers[i] = Integer.toString(time.monthDay++);
    301             time.normalize(true);
    302         }
    303         // We do one extra add at the end of the loop, if that pushed us to a
    304         // new month undo it
    305         if (time.monthDay == 1) {
    306             time.monthDay--;
    307             time.normalize(true);
    308         }
    309         mLastMonth = time.month;
    310 
    311         updateSelectionPositions();
    312     }
    313 
    314     /**
    315      * Sets up the text and style properties for painting. Override this if you
    316      * want to use a different paint.
    317      */
    318     protected void initView() {
    319         p.setFakeBoldText(false);
    320         p.setAntiAlias(true);
    321         p.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
    322         p.setStyle(Style.FILL);
    323 
    324         mMonthNumPaint = new Paint();
    325         mMonthNumPaint.setFakeBoldText(true);
    326         mMonthNumPaint.setAntiAlias(true);
    327         mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
    328         mMonthNumPaint.setColor(mFocusMonthColor);
    329         mMonthNumPaint.setStyle(Style.FILL);
    330         mMonthNumPaint.setTextAlign(Align.CENTER);
    331     }
    332 
    333     /**
    334      * Returns the month of the first day in this week
    335      *
    336      * @return The month the first day of this view is in
    337      */
    338     public int getFirstMonth() {
    339         return mFirstMonth;
    340     }
    341 
    342     /**
    343      * Returns the month of the last day in this week
    344      *
    345      * @return The month the last day of this view is in
    346      */
    347     public int getLastMonth() {
    348         return mLastMonth;
    349     }
    350 
    351     /**
    352      * Returns the julian day of the first day in this view.
    353      *
    354      * @return The julian day of the first day in the view.
    355      */
    356     public int getFirstJulianDay() {
    357         return mFirstJulianDay;
    358     }
    359 
    360     /**
    361      * Calculates the day that the given x position is in, accounting for week
    362      * number. Returns a Time referencing that day or null if
    363      *
    364      * @param x The x position of the touch event
    365      * @return A time object for the tapped day or null if the position wasn't
    366      *         in a day
    367      */
    368     public Time getDayFromLocation(float x) {
    369         int dayStart = mShowWeekNum ? (mWidth - mPadding * 2) / mNumCells + mPadding : mPadding;
    370         if (x < dayStart || x > mWidth - mPadding) {
    371             return null;
    372         }
    373         // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
    374         int dayPosition = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
    375         int day = mFirstJulianDay + dayPosition;
    376 
    377         Time time = new Time(mTimeZone);
    378         if (mWeek == 0) {
    379             // This week is weird...
    380             if (day < Time.EPOCH_JULIAN_DAY) {
    381                 day++;
    382             } else if (day == Time.EPOCH_JULIAN_DAY) {
    383                 time.set(1, 0, 1970);
    384                 time.normalize(true);
    385                 return time;
    386             }
    387         }
    388 
    389         time.setJulianDay(day);
    390         return time;
    391     }
    392 
    393     @Override
    394     protected void onDraw(Canvas canvas) {
    395         drawBackground(canvas);
    396         drawWeekNums(canvas);
    397         drawDaySeparators(canvas);
    398     }
    399 
    400     /**
    401      * This draws the selection highlight if a day is selected in this week.
    402      * Override this method if you wish to have a different background drawn.
    403      *
    404      * @param canvas The canvas to draw on
    405      */
    406     protected void drawBackground(Canvas canvas) {
    407         if (mHasSelectedDay) {
    408             p.setColor(mSelectedWeekBGColor);
    409             p.setStyle(Style.FILL);
    410         } else {
    411             return;
    412         }
    413         r.top = 1;
    414         r.bottom = mHeight - 1;
    415         r.left = mPadding;
    416         r.right = mSelectedLeft;
    417         canvas.drawRect(r, p);
    418         r.left = mSelectedRight;
    419         r.right = mWidth - mPadding;
    420         canvas.drawRect(r, p);
    421     }
    422 
    423     /**
    424      * Draws the week and month day numbers for this week. Override this method
    425      * if you need different placement.
    426      *
    427      * @param canvas The canvas to draw on
    428      */
    429     protected void drawWeekNums(Canvas canvas) {
    430         int y = ((mHeight + MINI_DAY_NUMBER_TEXT_SIZE) / 2) - DAY_SEPARATOR_WIDTH;
    431         int nDays = mNumCells;
    432 
    433         int i = 0;
    434         int divisor = 2 * nDays;
    435         if (mShowWeekNum) {
    436             p.setTextSize(MINI_WK_NUMBER_TEXT_SIZE);
    437             p.setStyle(Style.FILL);
    438             p.setTextAlign(Align.CENTER);
    439             p.setAntiAlias(true);
    440             p.setColor(mWeekNumColor);
    441             int x = (mWidth - mPadding * 2) / divisor + mPadding;
    442             canvas.drawText(mDayNumbers[0], x, y, p);
    443             i++;
    444         }
    445 
    446         boolean isFocusMonth = mFocusDay[i];
    447         mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
    448         mMonthNumPaint.setFakeBoldText(false);
    449         for (; i < nDays; i++) {
    450             if (mFocusDay[i] != isFocusMonth) {
    451                 isFocusMonth = mFocusDay[i];
    452                 mMonthNumPaint.setColor(isFocusMonth ? mFocusMonthColor : mOtherMonthColor);
    453             }
    454             if (mHasToday && mToday == i) {
    455                 mMonthNumPaint.setTextSize(MINI_TODAY_NUMBER_TEXT_SIZE);
    456                 mMonthNumPaint.setFakeBoldText(true);
    457             }
    458             int x = (2 * i + 1) * (mWidth - mPadding * 2) / (divisor) + mPadding;
    459             canvas.drawText(mDayNumbers[i], x, y, mMonthNumPaint);
    460             if (mHasToday && mToday == i) {
    461                 mMonthNumPaint.setTextSize(MINI_DAY_NUMBER_TEXT_SIZE);
    462                 mMonthNumPaint.setFakeBoldText(false);
    463             }
    464         }
    465     }
    466 
    467     /**
    468      * Draws a horizontal line for separating the weeks. Override this method if
    469      * you want custom separators.
    470      *
    471      * @param canvas The canvas to draw on
    472      */
    473     protected void drawDaySeparators(Canvas canvas) {
    474         if (mHasSelectedDay) {
    475             r.top = 1;
    476             r.bottom = mHeight - 1;
    477             r.left = mSelectedLeft + 1;
    478             r.right = mSelectedRight - 1;
    479             p.setStrokeWidth(MINI_TODAY_OUTLINE_WIDTH);
    480             p.setStyle(Style.STROKE);
    481             p.setColor(mTodayOutlineColor);
    482             canvas.drawRect(r, p);
    483         }
    484         if (mShowWeekNum) {
    485             p.setColor(mDaySeparatorColor);
    486             p.setStrokeWidth(DAY_SEPARATOR_WIDTH);
    487 
    488             int x = (mWidth - mPadding * 2) / mNumCells + mPadding;
    489             canvas.drawLine(x, 0, x, mHeight, p);
    490         }
    491     }
    492 
    493     @Override
    494     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    495         mWidth = w;
    496         updateSelectionPositions();
    497     }
    498 
    499     /**
    500      * This calculates the positions for the selected day lines.
    501      */
    502     protected void updateSelectionPositions() {
    503         if (mHasSelectedDay) {
    504             int selectedPosition = mSelectedDay - mWeekStart;
    505             if (selectedPosition < 0) {
    506                 selectedPosition += 7;
    507             }
    508             if (mShowWeekNum) {
    509                 selectedPosition++;
    510             }
    511             mSelectedLeft = selectedPosition * (mWidth - mPadding * 2) / mNumCells
    512                     + mPadding;
    513             mSelectedRight = (selectedPosition + 1) * (mWidth - mPadding * 2) / mNumCells
    514                     + mPadding;
    515         }
    516     }
    517 
    518     @Override
    519     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    520         setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
    521     }
    522 
    523     @Override
    524     public boolean onHoverEvent(MotionEvent event) {
    525         Context context = getContext();
    526         // only send accessibility events if accessibility and exploration are
    527         // on.
    528         AccessibilityManager am = (AccessibilityManager) context
    529                 .getSystemService(Service.ACCESSIBILITY_SERVICE);
    530         if (!am.isEnabled() || !am.isTouchExplorationEnabled()) {
    531             return super.onHoverEvent(event);
    532         }
    533         if (event.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
    534             Time hover = getDayFromLocation(event.getX());
    535             if (hover != null
    536                     && (mLastHoverTime == null || Time.compare(hover, mLastHoverTime) != 0)) {
    537                 Long millis = hover.toMillis(true);
    538                 String date = Utils.formatDateRange(context, millis, millis,
    539                         DateUtils.FORMAT_SHOW_DATE);
    540                 AccessibilityEvent accessEvent =
    541                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
    542                 accessEvent.getText().add(date);
    543                 sendAccessibilityEventUnchecked(accessEvent);
    544                 mLastHoverTime = hover;
    545             }
    546         }
    547         return true;
    548     }
    549 
    550     Time mLastHoverTime = null;
    551 }