Home | History | Annotate | Download | only in recurrencepicker
      1 /*
      2  * Copyright (C) 2013 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.recurrencepicker;
     18 
     19 import android.app.Activity;
     20 import android.app.DialogFragment;
     21 import android.content.Context;
     22 import android.content.res.Configuration;
     23 import android.content.res.Resources;
     24 import android.os.Bundle;
     25 import android.os.Parcel;
     26 import android.os.Parcelable;
     27 import android.text.Editable;
     28 import android.text.TextUtils;
     29 import android.text.TextWatcher;
     30 import android.text.format.DateUtils;
     31 import android.text.format.Time;
     32 import android.util.Log;
     33 import android.util.TimeFormatException;
     34 import android.view.LayoutInflater;
     35 import android.view.View;
     36 import android.view.View.OnClickListener;
     37 import android.view.ViewGroup;
     38 import android.view.ViewGroup.LayoutParams;
     39 import android.view.Window;
     40 import android.widget.AdapterView;
     41 import android.widget.AdapterView.OnItemSelectedListener;
     42 import android.widget.ArrayAdapter;
     43 import android.widget.Button;
     44 import android.widget.CompoundButton;
     45 import android.widget.CompoundButton.OnCheckedChangeListener;
     46 import android.widget.EditText;
     47 import android.widget.LinearLayout;
     48 import android.widget.RadioButton;
     49 import android.widget.RadioGroup;
     50 import android.widget.Spinner;
     51 import android.widget.Switch;
     52 import android.widget.TableLayout;
     53 import android.widget.TextView;
     54 import android.widget.Toast;
     55 import android.widget.ToggleButton;
     56 
     57 import com.android.calendar.R;
     58 import com.android.calendar.Utils;
     59 import com.android.calendarcommon2.EventRecurrence;
     60 import com.android.datetimepicker.date.DatePickerDialog;
     61 
     62 import java.text.DateFormatSymbols;
     63 import java.util.ArrayList;
     64 import java.util.Arrays;
     65 import java.util.Calendar;
     66 
     67 public class RecurrencePickerDialog extends DialogFragment implements OnItemSelectedListener,
     68         OnCheckedChangeListener, OnClickListener,
     69         android.widget.RadioGroup.OnCheckedChangeListener, DatePickerDialog.OnDateSetListener {
     70 
     71     private static final String TAG = "RecurrencePickerDialog";
     72 
     73     // in dp's
     74     private static final int MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK = 450;
     75 
     76     // Update android:maxLength in EditText as needed
     77     private static final int INTERVAL_MAX = 99;
     78     private static final int INTERVAL_DEFAULT = 1;
     79     // Update android:maxLength in EditText as needed
     80     private static final int COUNT_MAX = 730;
     81     private static final int COUNT_DEFAULT = 5;
     82 
     83     private DatePickerDialog mDatePickerDialog;
     84 
     85     private class RecurrenceModel implements Parcelable {
     86 
     87         // Should match EventRecurrence.DAILY, etc
     88         static final int FREQ_DAILY = 0;
     89         static final int FREQ_WEEKLY = 1;
     90         static final int FREQ_MONTHLY = 2;
     91         static final int FREQ_YEARLY = 3;
     92 
     93         static final int END_NEVER = 0;
     94         static final int END_BY_DATE = 1;
     95         static final int END_BY_COUNT = 2;
     96 
     97         static final int MONTHLY_BY_DATE = 0;
     98         static final int MONTHLY_BY_NTH_DAY_OF_WEEK = 1;
     99 
    100         static final int STATE_NO_RECURRENCE = 0;
    101         static final int STATE_RECURRENCE = 1;
    102 
    103         int recurrenceState;
    104 
    105         /**
    106          * FREQ: Repeat pattern
    107          *
    108          * @see FREQ_DAILY
    109          * @see FREQ_WEEKLY
    110          * @see FREQ_MONTHLY
    111          * @see FREQ_YEARLY
    112          */
    113         int freq = FREQ_WEEKLY;
    114 
    115         /**
    116          * INTERVAL: Every n days/weeks/months/years. n >= 1
    117          */
    118         int interval = INTERVAL_DEFAULT;
    119 
    120         /**
    121          * UNTIL and COUNT: How does the the event end?
    122          *
    123          * @see END_NEVER
    124          * @see END_BY_DATE
    125          * @see END_BY_COUNT
    126          * @see untilDate
    127          * @see untilCount
    128          */
    129         int end;
    130 
    131         /**
    132          * UNTIL: Date of the last recurrence. Used when until == END_BY_DATE
    133          */
    134         Time endDate;
    135 
    136         /**
    137          * COUNT: Times to repeat. Use when until == END_BY_COUNT
    138          */
    139         int endCount = COUNT_DEFAULT;
    140 
    141         /**
    142          * BYDAY: Days of the week to be repeated. Sun = 0, Mon = 1, etc
    143          */
    144         boolean[] weeklyByDayOfWeek = new boolean[7];
    145 
    146         /**
    147          * BYDAY AND BYMONTHDAY: How to repeat monthly events? Same date of the
    148          * month or Same nth day of week.
    149          *
    150          * @see MONTHLY_BY_DATE
    151          * @see MONTHLY_BY_NTH_DAY_OF_WEEK
    152          */
    153         int monthlyRepeat;
    154 
    155         /**
    156          * Day of the month to repeat. Used when monthlyRepeat ==
    157          * MONTHLY_BY_DATE
    158          */
    159         int monthlyByMonthDay;
    160 
    161         /**
    162          * Day of the week to repeat. Used when monthlyRepeat ==
    163          * MONTHLY_BY_NTH_DAY_OF_WEEK
    164          */
    165         int monthlyByDayOfWeek;
    166 
    167         /**
    168          * Nth day of the week to repeat. Used when monthlyRepeat ==
    169          * MONTHLY_BY_NTH_DAY_OF_WEEK 0=undefined, 1=1st, 2=2nd, etc
    170          */
    171         int monthlyByNthDayOfWeek;
    172 
    173         /*
    174          * (generated method)
    175          */
    176         @Override
    177         public String toString() {
    178             return "Model [freq=" + freq + ", interval=" + interval + ", end=" + end + ", endDate="
    179                     + endDate + ", endCount=" + endCount + ", weeklyByDayOfWeek="
    180                     + Arrays.toString(weeklyByDayOfWeek) + ", monthlyRepeat=" + monthlyRepeat
    181                     + ", monthlyByMonthDay=" + monthlyByMonthDay + ", monthlyByDayOfWeek="
    182                     + monthlyByDayOfWeek + ", monthlyByNthDayOfWeek=" + monthlyByNthDayOfWeek + "]";
    183         }
    184 
    185         @Override
    186         public int describeContents() {
    187             return 0;
    188         }
    189 
    190         public RecurrenceModel() {
    191         }
    192 
    193         @Override
    194         public void writeToParcel(Parcel dest, int flags) {
    195             dest.writeInt(freq);
    196             dest.writeInt(interval);
    197             dest.writeInt(end);
    198             dest.writeInt(endDate.year);
    199             dest.writeInt(endDate.month);
    200             dest.writeInt(endDate.monthDay);
    201             dest.writeInt(endCount);
    202             dest.writeBooleanArray(weeklyByDayOfWeek);
    203             dest.writeInt(monthlyRepeat);
    204             dest.writeInt(monthlyByMonthDay);
    205             dest.writeInt(monthlyByDayOfWeek);
    206             dest.writeInt(monthlyByNthDayOfWeek);
    207             dest.writeInt(recurrenceState);
    208         }
    209     }
    210 
    211     class minMaxTextWatcher implements TextWatcher {
    212         private int mMin;
    213         private int mMax;
    214         private int mDefault;
    215 
    216         public minMaxTextWatcher(int min, int defaultInt, int max) {
    217             mMin = min;
    218             mMax = max;
    219             mDefault = defaultInt;
    220         }
    221 
    222         @Override
    223         public void afterTextChanged(Editable s) {
    224 
    225             boolean updated = false;
    226             int value;
    227             try {
    228                 value = Integer.parseInt(s.toString());
    229             } catch (NumberFormatException e) {
    230                 value = mDefault;
    231             }
    232 
    233             if (value < mMin) {
    234                 value = mMin;
    235                 updated = true;
    236             } else if (value > mMax) {
    237                 updated = true;
    238                 value = mMax;
    239             }
    240 
    241             // Update UI
    242             if (updated) {
    243                 s.clear();
    244                 s.append(Integer.toString(value));
    245             }
    246 
    247             updateDoneButtonState();
    248             onChange(value);
    249         }
    250 
    251         /** Override to be called after each key stroke */
    252         void onChange(int value) {
    253         }
    254 
    255         @Override
    256         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    257         }
    258 
    259         @Override
    260         public void onTextChanged(CharSequence s, int start, int before, int count) {
    261         }
    262     }
    263 
    264     private Resources mResources;
    265     private EventRecurrence mRecurrence = new EventRecurrence();
    266     private Time mTime = new Time(); // TODO timezone?
    267     private RecurrenceModel mModel = new RecurrenceModel();
    268     private Toast mToast;
    269 
    270     private final int[] TIME_DAY_TO_CALENDAR_DAY = new int[] {
    271             Calendar.SUNDAY,
    272             Calendar.MONDAY,
    273             Calendar.TUESDAY,
    274             Calendar.WEDNESDAY,
    275             Calendar.THURSDAY,
    276             Calendar.FRIDAY,
    277             Calendar.SATURDAY,
    278     };
    279 
    280     // Call mStringBuilder.setLength(0) before formatting any string or else the
    281     // formatted text will accumulate.
    282     // private final StringBuilder mStringBuilder = new StringBuilder();
    283     // private Formatter mFormatter = new Formatter(mStringBuilder);
    284 
    285     private View mView;
    286 
    287     private Spinner mFreqSpinner;
    288     private static final int[] mFreqModelToEventRecurrence = {
    289             EventRecurrence.DAILY,
    290             EventRecurrence.WEEKLY,
    291             EventRecurrence.MONTHLY,
    292             EventRecurrence.YEARLY
    293     };
    294 
    295     public static final String BUNDLE_START_TIME_MILLIS = "bundle_event_start_time";
    296     public static final String BUNDLE_TIME_ZONE = "bundle_event_time_zone";
    297     public static final String BUNDLE_RRULE = "bundle_event_rrule";
    298 
    299     private static final String BUNDLE_MODEL = "bundle_model";
    300     private static final String BUNDLE_END_COUNT_HAS_FOCUS = "bundle_end_count_has_focus";
    301 
    302     private static final String FRAG_TAG_DATE_PICKER = "tag_date_picker_frag";
    303 
    304     private Switch mRepeatSwitch;
    305 
    306     private EditText mInterval;
    307     private TextView mIntervalPreText;
    308     private TextView mIntervalPostText;
    309 
    310     private int mIntervalResId = -1;
    311 
    312     private Spinner mEndSpinner;
    313     private TextView mEndDateTextView;
    314     private EditText mEndCount;
    315     private TextView mPostEndCount;
    316     private boolean mHidePostEndCount;
    317 
    318     private ArrayList<CharSequence> mEndSpinnerArray = new ArrayList<CharSequence>(3);
    319     private EndSpinnerAdapter mEndSpinnerAdapter;
    320     private String mEndNeverStr;
    321     private String mEndDateLabel;
    322     private String mEndCountLabel;
    323 
    324     /** Hold toggle buttons in the order per user's first day of week preference */
    325     private LinearLayout mWeekGroup;
    326     private LinearLayout mWeekGroup2;
    327     // Sun = 0
    328     private ToggleButton[] mWeekByDayButtons = new ToggleButton[7];
    329     /** A double array of Strings to hold the 7x5 list of possible strings of the form:
    330      *  "on every [Nth] [DAY_OF_WEEK]", e.g. "on every second Monday",
    331      *  where [Nth] can be [first, second, third, fourth, last] */
    332     private String[][] mMonthRepeatByDayOfWeekStrs;
    333 
    334     private LinearLayout mMonthGroup;
    335     private RadioGroup mMonthRepeatByRadioGroup;
    336     private RadioButton mRepeatMonthlyByNthDayOfWeek;
    337     private RadioButton mRepeatMonthlyByNthDayOfMonth;
    338     private String mMonthRepeatByDayOfWeekStr;
    339 
    340     private Button mDone;
    341 
    342     private OnRecurrenceSetListener mRecurrenceSetListener;
    343 
    344     public RecurrencePickerDialog() {
    345     }
    346 
    347     static public boolean canHandleRecurrenceRule(EventRecurrence er) {
    348         switch (er.freq) {
    349             case EventRecurrence.DAILY:
    350             case EventRecurrence.MONTHLY:
    351             case EventRecurrence.YEARLY:
    352             case EventRecurrence.WEEKLY:
    353                 break;
    354             default:
    355                 return false;
    356         }
    357 
    358         if (er.count > 0 && !TextUtils.isEmpty(er.until)) {
    359             return false;
    360         }
    361 
    362         // Weekly: For "repeat by day of week", the day of week to repeat is in
    363         // er.byday[]
    364 
    365         /*
    366          * Monthly: For "repeat by nth day of week" the day of week to repeat is
    367          * in er.byday[] and the "nth" is stored in er.bydayNum[]. Currently we
    368          * can handle only one and only in monthly
    369          */
    370         int numOfByDayNum = 0;
    371         for (int i = 0; i < er.bydayCount; i++) {
    372             if (er.bydayNum[i] > 0) {
    373                 ++numOfByDayNum;
    374             }
    375         }
    376 
    377         if (numOfByDayNum > 1) {
    378             return false;
    379         }
    380 
    381         if (numOfByDayNum > 0 && er.freq != EventRecurrence.MONTHLY) {
    382             return false;
    383         }
    384 
    385         // The UI only handle repeat by one day of month i.e. not 9th and 10th
    386         // of every month
    387         if (er.bymonthdayCount > 1) {
    388             return false;
    389         }
    390 
    391         if (er.freq == EventRecurrence.MONTHLY) {
    392             if (er.bydayCount > 1) {
    393                 return false;
    394             }
    395             if (er.bydayCount > 0 && er.bymonthdayCount > 0) {
    396                 return false;
    397             }
    398         }
    399 
    400         return true;
    401     }
    402 
    403     // TODO don't lose data when getting data that our UI can't handle
    404     static private void copyEventRecurrenceToModel(final EventRecurrence er,
    405             RecurrenceModel model) {
    406         // Freq:
    407         switch (er.freq) {
    408             case EventRecurrence.DAILY:
    409                 model.freq = RecurrenceModel.FREQ_DAILY;
    410                 break;
    411             case EventRecurrence.MONTHLY:
    412                 model.freq = RecurrenceModel.FREQ_MONTHLY;
    413                 break;
    414             case EventRecurrence.YEARLY:
    415                 model.freq = RecurrenceModel.FREQ_YEARLY;
    416                 break;
    417             case EventRecurrence.WEEKLY:
    418                 model.freq = RecurrenceModel.FREQ_WEEKLY;
    419                 break;
    420             default:
    421                 throw new IllegalStateException("freq=" + er.freq);
    422         }
    423 
    424         // Interval:
    425         if (er.interval > 0) {
    426             model.interval = er.interval;
    427         }
    428 
    429         // End:
    430         // End by count:
    431         model.endCount = er.count;
    432         if (model.endCount > 0) {
    433             model.end = RecurrenceModel.END_BY_COUNT;
    434         }
    435 
    436         // End by date:
    437         if (!TextUtils.isEmpty(er.until)) {
    438             if (model.endDate == null) {
    439                 model.endDate = new Time();
    440             }
    441 
    442             try {
    443                 model.endDate.parse(er.until);
    444             } catch (TimeFormatException e) {
    445                 model.endDate = null;
    446             }
    447 
    448             // LIMITATION: The UI can only handle END_BY_DATE or END_BY_COUNT
    449             if (model.end == RecurrenceModel.END_BY_COUNT && model.endDate != null) {
    450                 throw new IllegalStateException("freq=" + er.freq);
    451             }
    452 
    453             model.end = RecurrenceModel.END_BY_DATE;
    454         }
    455 
    456         // Weekly: repeat by day of week or Monthly: repeat by nth day of week
    457         // in the month
    458         Arrays.fill(model.weeklyByDayOfWeek, false);
    459         if (er.bydayCount > 0) {
    460             int count = 0;
    461             for (int i = 0; i < er.bydayCount; i++) {
    462                 int dayOfWeek = EventRecurrence.day2TimeDay(er.byday[i]);
    463                 model.weeklyByDayOfWeek[dayOfWeek] = true;
    464 
    465                 if (model.freq == RecurrenceModel.FREQ_MONTHLY && er.bydayNum[i] > 0) {
    466                     // LIMITATION: Can handle only (one) weekDayNum and only
    467                     // when
    468                     // monthly
    469                     model.monthlyByDayOfWeek = dayOfWeek;
    470                     model.monthlyByNthDayOfWeek = er.bydayNum[i];
    471                     model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK;
    472                     count++;
    473                 }
    474             }
    475 
    476             if (model.freq == RecurrenceModel.FREQ_MONTHLY) {
    477                 if (er.bydayCount != 1) {
    478                     // Can't handle 1st Monday and 2nd Wed
    479                     throw new IllegalStateException("Can handle only 1 byDayOfWeek in monthly");
    480                 }
    481                 if (count != 1) {
    482                     throw new IllegalStateException(
    483                             "Didn't specify which nth day of week to repeat for a monthly");
    484                 }
    485             }
    486         }
    487 
    488         // Monthly by day of month
    489         if (model.freq == RecurrenceModel.FREQ_MONTHLY) {
    490             if (er.bymonthdayCount == 1) {
    491                 if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
    492                     throw new IllegalStateException(
    493                             "Can handle only by monthday or by nth day of week, not both");
    494                 }
    495                 model.monthlyByMonthDay = er.bymonthday[0];
    496                 model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE;
    497             } else if (er.bymonthCount > 1) {
    498                 // LIMITATION: Can handle only one month day
    499                 throw new IllegalStateException("Can handle only one bymonthday");
    500             }
    501         }
    502     }
    503 
    504     static private void copyModelToEventRecurrence(final RecurrenceModel model,
    505             EventRecurrence er) {
    506         if (model.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
    507             throw new IllegalStateException("There's no recurrence");
    508         }
    509 
    510         // Freq
    511         er.freq = mFreqModelToEventRecurrence[model.freq];
    512 
    513         // Interval
    514         if (model.interval <= 1) {
    515             er.interval = 0;
    516         } else {
    517             er.interval = model.interval;
    518         }
    519 
    520         // End
    521         switch (model.end) {
    522             case RecurrenceModel.END_BY_DATE:
    523                 if (model.endDate != null) {
    524                     model.endDate.switchTimezone(Time.TIMEZONE_UTC);
    525                     model.endDate.normalize(false);
    526                     er.until = model.endDate.format2445();
    527                     er.count = 0;
    528                 } else {
    529                     throw new IllegalStateException("end = END_BY_DATE but endDate is null");
    530                 }
    531                 break;
    532             case RecurrenceModel.END_BY_COUNT:
    533                 er.count = model.endCount;
    534                 er.until = null;
    535                 if (er.count <= 0) {
    536                     throw new IllegalStateException("count is " + er.count);
    537                 }
    538                 break;
    539             default:
    540                 er.count = 0;
    541                 er.until = null;
    542                 break;
    543         }
    544 
    545         // Weekly && monthly repeat patterns
    546         er.bydayCount = 0;
    547         er.bymonthdayCount = 0;
    548 
    549         switch (model.freq) {
    550             case RecurrenceModel.FREQ_MONTHLY:
    551                 if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) {
    552                     if (model.monthlyByMonthDay > 0) {
    553                         if (er.bymonthday == null || er.bymonthdayCount < 1) {
    554                             er.bymonthday = new int[1];
    555                         }
    556                         er.bymonthday[0] = model.monthlyByMonthDay;
    557                         er.bymonthdayCount = 1;
    558                     }
    559                 } else if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
    560                     if (model.monthlyByNthDayOfWeek <= 0) {
    561                         throw new IllegalStateException("month repeat by nth week but n is "
    562                                 + model.monthlyByNthDayOfWeek);
    563                     }
    564                     int count = 1;
    565                     if (er.bydayCount < count || er.byday == null || er.bydayNum == null) {
    566                         er.byday = new int[count];
    567                         er.bydayNum = new int[count];
    568                     }
    569                     er.bydayCount = count;
    570                     er.byday[0] = EventRecurrence.timeDay2Day(model.monthlyByDayOfWeek);
    571                     er.bydayNum[0] = model.monthlyByNthDayOfWeek;
    572                 }
    573                 break;
    574             case RecurrenceModel.FREQ_WEEKLY:
    575                 int count = 0;
    576                 for (int i = 0; i < 7; i++) {
    577                     if (model.weeklyByDayOfWeek[i]) {
    578                         count++;
    579                     }
    580                 }
    581 
    582                 if (er.bydayCount < count || er.byday == null || er.bydayNum == null) {
    583                     er.byday = new int[count];
    584                     er.bydayNum = new int[count];
    585                 }
    586                 er.bydayCount = count;
    587 
    588                 for (int i = 6; i >= 0; i--) {
    589                     if (model.weeklyByDayOfWeek[i]) {
    590                         er.bydayNum[--count] = 0;
    591                         er.byday[count] = EventRecurrence.timeDay2Day(i);
    592                     }
    593                 }
    594                 break;
    595         }
    596 
    597         if (!canHandleRecurrenceRule(er)) {
    598             throw new IllegalStateException("UI generated recurrence that it can't handle. ER:"
    599                     + er.toString() + " Model: " + model.toString());
    600         }
    601     }
    602 
    603     @Override
    604     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    605             Bundle savedInstanceState) {
    606         mRecurrence.wkst = EventRecurrence.timeDay2Day(Utils.getFirstDayOfWeek(getActivity()));
    607 
    608         getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
    609 
    610         boolean endCountHasFocus = false;
    611         if (savedInstanceState != null) {
    612             RecurrenceModel m = (RecurrenceModel) savedInstanceState.get(BUNDLE_MODEL);
    613             if (m != null) {
    614                 mModel = m;
    615             }
    616             endCountHasFocus = savedInstanceState.getBoolean(BUNDLE_END_COUNT_HAS_FOCUS);
    617         } else {
    618             Bundle b = getArguments();
    619             if (b != null) {
    620                 mTime.set(b.getLong(BUNDLE_START_TIME_MILLIS));
    621 
    622                 String tz = b.getString(BUNDLE_TIME_ZONE);
    623                 if (!TextUtils.isEmpty(tz)) {
    624                     mTime.timezone = tz;
    625                 }
    626                 mTime.normalize(false);
    627 
    628                 // Time days of week: Sun=0, Mon=1, etc
    629                 mModel.weeklyByDayOfWeek[mTime.weekDay] = true;
    630                 String rrule = b.getString(BUNDLE_RRULE);
    631                 if (!TextUtils.isEmpty(rrule)) {
    632                     mModel.recurrenceState = RecurrenceModel.STATE_RECURRENCE;
    633                     mRecurrence.parse(rrule);
    634                     copyEventRecurrenceToModel(mRecurrence, mModel);
    635                     // Leave today's day of week as checked by default in weekly view.
    636                     if (mRecurrence.bydayCount == 0) {
    637                         mModel.weeklyByDayOfWeek[mTime.weekDay] = true;
    638                     }
    639                 }
    640 
    641             } else {
    642                 mTime.setToNow();
    643             }
    644         }
    645 
    646         mResources = getResources();
    647         mView = inflater.inflate(R.layout.recurrencepicker, container, true);
    648 
    649         final Activity activity = getActivity();
    650         final Configuration config = activity.getResources().getConfiguration();
    651 
    652         mRepeatSwitch = (Switch) mView.findViewById(R.id.repeat_switch);
    653         mRepeatSwitch.setChecked(mModel.recurrenceState == RecurrenceModel.STATE_RECURRENCE);
    654         mRepeatSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    655 
    656             @Override
    657             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    658                 mModel.recurrenceState = isChecked ? RecurrenceModel.STATE_RECURRENCE
    659                         : RecurrenceModel.STATE_NO_RECURRENCE;
    660                 togglePickerOptions();
    661             }
    662         });
    663 
    664         mFreqSpinner = (Spinner) mView.findViewById(R.id.freqSpinner);
    665         mFreqSpinner.setOnItemSelectedListener(this);
    666         ArrayAdapter<CharSequence> freqAdapter = ArrayAdapter.createFromResource(getActivity(),
    667                 R.array.recurrence_freq, R.layout.recurrencepicker_freq_item);
    668         freqAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item);
    669         mFreqSpinner.setAdapter(freqAdapter);
    670 
    671         mInterval = (EditText) mView.findViewById(R.id.interval);
    672         mInterval.addTextChangedListener(new minMaxTextWatcher(1, INTERVAL_DEFAULT, INTERVAL_MAX) {
    673             @Override
    674             void onChange(int v) {
    675                 if (mIntervalResId != -1 && mInterval.getText().toString().length() > 0) {
    676                     mModel.interval = v;
    677                     updateIntervalText();
    678                     mInterval.requestLayout();
    679                 }
    680             }
    681         });
    682         mIntervalPreText = (TextView) mView.findViewById(R.id.intervalPreText);
    683         mIntervalPostText = (TextView) mView.findViewById(R.id.intervalPostText);
    684 
    685         mEndNeverStr = mResources.getString(R.string.recurrence_end_continously);
    686         mEndDateLabel = mResources.getString(R.string.recurrence_end_date_label);
    687         mEndCountLabel = mResources.getString(R.string.recurrence_end_count_label);
    688 
    689         mEndSpinnerArray.add(mEndNeverStr);
    690         mEndSpinnerArray.add(mEndDateLabel);
    691         mEndSpinnerArray.add(mEndCountLabel);
    692         mEndSpinner = (Spinner) mView.findViewById(R.id.endSpinner);
    693         mEndSpinner.setOnItemSelectedListener(this);
    694         mEndSpinnerAdapter = new EndSpinnerAdapter(getActivity(), mEndSpinnerArray,
    695                 R.layout.recurrencepicker_freq_item, R.layout.recurrencepicker_end_text);
    696         mEndSpinnerAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item);
    697         mEndSpinner.setAdapter(mEndSpinnerAdapter);
    698 
    699         mEndCount = (EditText) mView.findViewById(R.id.endCount);
    700         mEndCount.addTextChangedListener(new minMaxTextWatcher(1, COUNT_DEFAULT, COUNT_MAX) {
    701             @Override
    702             void onChange(int v) {
    703                 if (mModel.endCount != v) {
    704                     mModel.endCount = v;
    705                     updateEndCountText();
    706                     mEndCount.requestLayout();
    707                 }
    708             }
    709         });
    710         mPostEndCount = (TextView) mView.findViewById(R.id.postEndCount);
    711 
    712         mEndDateTextView = (TextView) mView.findViewById(R.id.endDate);
    713         mEndDateTextView.setOnClickListener(this);
    714         if (mModel.endDate == null) {
    715             mModel.endDate = new Time(mTime);
    716             switch (mModel.freq) {
    717                 case RecurrenceModel.FREQ_DAILY:
    718                 case RecurrenceModel.FREQ_WEEKLY:
    719                     mModel.endDate.month += 1;
    720                     break;
    721                 case RecurrenceModel.FREQ_MONTHLY:
    722                     mModel.endDate.month += 3;
    723                     break;
    724                 case RecurrenceModel.FREQ_YEARLY:
    725                     mModel.endDate.year += 3;
    726                     break;
    727             }
    728             mModel.endDate.normalize(false);
    729         }
    730 
    731         mWeekGroup = (LinearLayout) mView.findViewById(R.id.weekGroup);
    732         mWeekGroup2 = (LinearLayout) mView.findViewById(R.id.weekGroup2);
    733 
    734         // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7
    735         String[] dayOfWeekString = new DateFormatSymbols().getWeekdays();
    736 
    737         mMonthRepeatByDayOfWeekStrs = new String[7][];
    738         // from Time.SUNDAY as 0 through Time.SATURDAY as 6
    739         mMonthRepeatByDayOfWeekStrs[0] = mResources.getStringArray(R.array.repeat_by_nth_sun);
    740         mMonthRepeatByDayOfWeekStrs[1] = mResources.getStringArray(R.array.repeat_by_nth_mon);
    741         mMonthRepeatByDayOfWeekStrs[2] = mResources.getStringArray(R.array.repeat_by_nth_tues);
    742         mMonthRepeatByDayOfWeekStrs[3] = mResources.getStringArray(R.array.repeat_by_nth_wed);
    743         mMonthRepeatByDayOfWeekStrs[4] = mResources.getStringArray(R.array.repeat_by_nth_thurs);
    744         mMonthRepeatByDayOfWeekStrs[5] = mResources.getStringArray(R.array.repeat_by_nth_fri);
    745         mMonthRepeatByDayOfWeekStrs[6] = mResources.getStringArray(R.array.repeat_by_nth_sat);
    746 
    747         // In Time.java day of week order e.g. Sun = 0
    748         int idx = Utils.getFirstDayOfWeek(getActivity());
    749 
    750         // In Calendar.java day of week order e.g Sun = 1 ... Sat = 7
    751         dayOfWeekString = new DateFormatSymbols().getShortWeekdays();
    752 
    753         int numOfButtonsInRow1;
    754         int numOfButtonsInRow2;
    755 
    756         if (mResources.getConfiguration().screenWidthDp > MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK) {
    757             numOfButtonsInRow1 = 7;
    758             numOfButtonsInRow2 = 0;
    759             mWeekGroup2.setVisibility(View.GONE);
    760             mWeekGroup2.getChildAt(3).setVisibility(View.GONE);
    761         } else {
    762             numOfButtonsInRow1 = 4;
    763             numOfButtonsInRow2 = 3;
    764 
    765             mWeekGroup2.setVisibility(View.VISIBLE);
    766             // Set rightmost button on the second row invisible so it takes up
    767             // space and everything centers properly
    768             mWeekGroup2.getChildAt(3).setVisibility(View.INVISIBLE);
    769         }
    770 
    771         /* First row */
    772         for (int i = 0; i < 7; i++) {
    773             if (i >= numOfButtonsInRow1) {
    774                 mWeekGroup.getChildAt(i).setVisibility(View.GONE);
    775                 continue;
    776             }
    777 
    778             mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup.getChildAt(i);
    779             mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
    780             mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
    781             mWeekByDayButtons[idx].setOnCheckedChangeListener(this);
    782 
    783             if (++idx >= 7) {
    784                 idx = 0;
    785             }
    786         }
    787 
    788         /* 2nd Row */
    789         for (int i = 0; i < 3; i++) {
    790             if (i >= numOfButtonsInRow2) {
    791                 mWeekGroup2.getChildAt(i).setVisibility(View.GONE);
    792                 continue;
    793             }
    794             mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup2.getChildAt(i);
    795             mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
    796             mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
    797             mWeekByDayButtons[idx].setOnCheckedChangeListener(this);
    798 
    799             if (++idx >= 7) {
    800                 idx = 0;
    801             }
    802         }
    803 
    804         mMonthGroup = (LinearLayout) mView.findViewById(R.id.monthGroup);
    805         mMonthRepeatByRadioGroup = (RadioGroup) mView.findViewById(R.id.monthGroup);
    806         mMonthRepeatByRadioGroup.setOnCheckedChangeListener(this);
    807         mRepeatMonthlyByNthDayOfWeek = (RadioButton) mView
    808                 .findViewById(R.id.repeatMonthlyByNthDayOfTheWeek);
    809         mRepeatMonthlyByNthDayOfMonth = (RadioButton) mView
    810                 .findViewById(R.id.repeatMonthlyByNthDayOfMonth);
    811 
    812         mDone = (Button) mView.findViewById(R.id.done);
    813         mDone.setOnClickListener(this);
    814 
    815         togglePickerOptions();
    816         updateDialog();
    817         if (endCountHasFocus) {
    818             mEndCount.requestFocus();
    819         }
    820         return mView;
    821     }
    822 
    823     private void togglePickerOptions() {
    824         if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
    825             mFreqSpinner.setEnabled(false);
    826             mEndSpinner.setEnabled(false);
    827             mIntervalPreText.setEnabled(false);
    828             mInterval.setEnabled(false);
    829             mIntervalPostText.setEnabled(false);
    830             mMonthRepeatByRadioGroup.setEnabled(false);
    831             mEndCount.setEnabled(false);
    832             mPostEndCount.setEnabled(false);
    833             mEndDateTextView.setEnabled(false);
    834             mRepeatMonthlyByNthDayOfWeek.setEnabled(false);
    835             mRepeatMonthlyByNthDayOfMonth.setEnabled(false);
    836             for (Button button : mWeekByDayButtons) {
    837                 button.setEnabled(false);
    838             }
    839         } else {
    840             mView.findViewById(R.id.options).setEnabled(true);
    841             mFreqSpinner.setEnabled(true);
    842             mEndSpinner.setEnabled(true);
    843             mIntervalPreText.setEnabled(true);
    844             mInterval.setEnabled(true);
    845             mIntervalPostText.setEnabled(true);
    846             mMonthRepeatByRadioGroup.setEnabled(true);
    847             mEndCount.setEnabled(true);
    848             mPostEndCount.setEnabled(true);
    849             mEndDateTextView.setEnabled(true);
    850             mRepeatMonthlyByNthDayOfWeek.setEnabled(true);
    851             mRepeatMonthlyByNthDayOfMonth.setEnabled(true);
    852             for (Button button : mWeekByDayButtons) {
    853                 button.setEnabled(true);
    854             }
    855         }
    856         updateDoneButtonState();
    857     }
    858 
    859     private void updateDoneButtonState() {
    860         if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
    861             mDone.setEnabled(true);
    862             return;
    863         }
    864 
    865         if (mInterval.getText().toString().length() == 0) {
    866             mDone.setEnabled(false);
    867             return;
    868         }
    869 
    870         if (mEndCount.getVisibility() == View.VISIBLE &&
    871                 mEndCount.getText().toString().length() == 0) {
    872             mDone.setEnabled(false);
    873             return;
    874         }
    875 
    876         if (mModel.freq == RecurrenceModel.FREQ_WEEKLY) {
    877             for (CompoundButton b : mWeekByDayButtons) {
    878                 if (b.isChecked()) {
    879                     mDone.setEnabled(true);
    880                     return;
    881                 }
    882             }
    883             mDone.setEnabled(false);
    884             return;
    885         }
    886 
    887         mDone.setEnabled(true);
    888     }
    889 
    890     @Override
    891     public void onSaveInstanceState(Bundle outState) {
    892         super.onSaveInstanceState(outState);
    893         outState.putParcelable(BUNDLE_MODEL, mModel);
    894         if (mEndCount.hasFocus()) {
    895             outState.putBoolean(BUNDLE_END_COUNT_HAS_FOCUS, true);
    896         }
    897     }
    898 
    899     public void updateDialog() {
    900         // Interval
    901         // Checking before setting because this causes infinite recursion
    902         // in afterTextWatcher
    903         final String intervalStr = Integer.toString(mModel.interval);
    904         if (!intervalStr.equals(mInterval.getText().toString())) {
    905             mInterval.setText(intervalStr);
    906         }
    907 
    908         mFreqSpinner.setSelection(mModel.freq);
    909         mWeekGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE);
    910         mWeekGroup2.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE);
    911         mMonthGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_MONTHLY ? View.VISIBLE : View.GONE);
    912 
    913         switch (mModel.freq) {
    914             case RecurrenceModel.FREQ_DAILY:
    915                 mIntervalResId = R.plurals.recurrence_interval_daily;
    916                 break;
    917 
    918             case RecurrenceModel.FREQ_WEEKLY:
    919                 mIntervalResId = R.plurals.recurrence_interval_weekly;
    920                 for (int i = 0; i < 7; i++) {
    921                     mWeekByDayButtons[i].setChecked(mModel.weeklyByDayOfWeek[i]);
    922                 }
    923                 break;
    924 
    925             case RecurrenceModel.FREQ_MONTHLY:
    926                 mIntervalResId = R.plurals.recurrence_interval_monthly;
    927 
    928                 if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) {
    929                     mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfMonth);
    930                 } else if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
    931                     mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfTheWeek);
    932                 }
    933 
    934                 if (mMonthRepeatByDayOfWeekStr == null) {
    935                     if (mModel.monthlyByNthDayOfWeek == 0) {
    936                         mModel.monthlyByNthDayOfWeek = (mTime.monthDay + 6) / 7;
    937                         mModel.monthlyByDayOfWeek = mTime.weekDay;
    938                     }
    939 
    940                     String[] monthlyByNthDayOfWeekStrs =
    941                             mMonthRepeatByDayOfWeekStrs[mModel.monthlyByDayOfWeek];
    942                     mMonthRepeatByDayOfWeekStr =
    943                             monthlyByNthDayOfWeekStrs[mModel.monthlyByNthDayOfWeek - 1];
    944                     mRepeatMonthlyByNthDayOfWeek.setText(mMonthRepeatByDayOfWeekStr);
    945                 }
    946                 break;
    947 
    948             case RecurrenceModel.FREQ_YEARLY:
    949                 mIntervalResId = R.plurals.recurrence_interval_yearly;
    950                 break;
    951         }
    952         updateIntervalText();
    953         updateDoneButtonState();
    954 
    955         mEndSpinner.setSelection(mModel.end);
    956         if (mModel.end == RecurrenceModel.END_BY_DATE) {
    957             final String dateStr = DateUtils.formatDateTime(getActivity(),
    958                     mModel.endDate.toMillis(false), DateUtils.FORMAT_NUMERIC_DATE);
    959             mEndDateTextView.setText(dateStr);
    960         } else {
    961             if (mModel.end == RecurrenceModel.END_BY_COUNT) {
    962                 // Checking before setting because this causes infinite
    963                 // recursion
    964                 // in afterTextWatcher
    965                 final String countStr = Integer.toString(mModel.endCount);
    966                 if (!countStr.equals(mEndCount.getText().toString())) {
    967                     mEndCount.setText(countStr);
    968                 }
    969             }
    970         }
    971     }
    972 
    973     /**
    974      * @param endDateString
    975      */
    976     private void setEndSpinnerEndDateStr(final String endDateString) {
    977         mEndSpinnerArray.set(1, endDateString);
    978         mEndSpinnerAdapter.notifyDataSetChanged();
    979     }
    980 
    981     private void doToast() {
    982         Log.e(TAG, "Model = " + mModel.toString());
    983         String rrule;
    984         if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
    985             rrule = "Not repeating";
    986         } else {
    987             copyModelToEventRecurrence(mModel, mRecurrence);
    988             rrule = mRecurrence.toString();
    989         }
    990 
    991         if (mToast != null) {
    992             mToast.cancel();
    993         }
    994         mToast = Toast.makeText(getActivity(), rrule,
    995                 Toast.LENGTH_LONG);
    996         mToast.show();
    997     }
    998 
    999     // TODO Test and update for Right-to-Left
   1000     private void updateIntervalText() {
   1001         if (mIntervalResId == -1) {
   1002             return;
   1003         }
   1004 
   1005         final String INTERVAL_COUNT_MARKER = "%d";
   1006         String intervalString = mResources.getQuantityString(mIntervalResId, mModel.interval);
   1007         int markerStart = intervalString.indexOf(INTERVAL_COUNT_MARKER);
   1008 
   1009         if (markerStart != -1) {
   1010           int postTextStart = markerStart + INTERVAL_COUNT_MARKER.length();
   1011           mIntervalPostText.setText(intervalString.substring(postTextStart,
   1012                   intervalString.length()).trim());
   1013           mIntervalPreText.setText(intervalString.substring(0, markerStart).trim());
   1014         }
   1015     }
   1016 
   1017     /**
   1018      * Update the "Repeat for N events" end option with the proper string values
   1019      * based on the value that has been entered for N.
   1020      */
   1021     private void updateEndCountText() {
   1022         final String END_COUNT_MARKER = "%d";
   1023         String endString = mResources.getQuantityString(R.plurals.recurrence_end_count,
   1024                 mModel.endCount);
   1025         int markerStart = endString.indexOf(END_COUNT_MARKER);
   1026 
   1027         if (markerStart != -1) {
   1028             if (markerStart == 0) {
   1029                 Log.e(TAG, "No text to put in to recurrence's end spinner.");
   1030             } else {
   1031                 int postTextStart = markerStart + END_COUNT_MARKER.length();
   1032                 mPostEndCount.setText(endString.substring(postTextStart,
   1033                         endString.length()).trim());
   1034             }
   1035         }
   1036     }
   1037 
   1038     // Implements OnItemSelectedListener interface
   1039     // Freq spinner
   1040     // End spinner
   1041     @Override
   1042     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
   1043         if (parent == mFreqSpinner) {
   1044             mModel.freq = position;
   1045         } else if (parent == mEndSpinner) {
   1046             switch (position) {
   1047                 case RecurrenceModel.END_NEVER:
   1048                     mModel.end = RecurrenceModel.END_NEVER;
   1049                     break;
   1050                 case RecurrenceModel.END_BY_DATE:
   1051                     mModel.end = RecurrenceModel.END_BY_DATE;
   1052                     break;
   1053                 case RecurrenceModel.END_BY_COUNT:
   1054                     mModel.end = RecurrenceModel.END_BY_COUNT;
   1055 
   1056                     if (mModel.endCount <= 1) {
   1057                         mModel.endCount = 1;
   1058                     } else if (mModel.endCount > COUNT_MAX) {
   1059                         mModel.endCount = COUNT_MAX;
   1060                     }
   1061                     updateEndCountText();
   1062                     break;
   1063             }
   1064             mEndCount.setVisibility(mModel.end == RecurrenceModel.END_BY_COUNT ? View.VISIBLE
   1065                     : View.GONE);
   1066             mEndDateTextView.setVisibility(mModel.end == RecurrenceModel.END_BY_DATE ? View.VISIBLE
   1067                     : View.GONE);
   1068             mPostEndCount.setVisibility(
   1069                     mModel.end == RecurrenceModel.END_BY_COUNT  && !mHidePostEndCount?
   1070                             View.VISIBLE : View.GONE);
   1071 
   1072         }
   1073         updateDialog();
   1074     }
   1075 
   1076     // Implements OnItemSelectedListener interface
   1077     @Override
   1078     public void onNothingSelected(AdapterView<?> arg0) {
   1079     }
   1080 
   1081     @Override
   1082     public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
   1083         if (mModel.endDate == null) {
   1084             mModel.endDate = new Time(mTime.timezone);
   1085             mModel.endDate.hour = mModel.endDate.minute = mModel.endDate.second = 0;
   1086         }
   1087         mModel.endDate.year = year;
   1088         mModel.endDate.month = monthOfYear;
   1089         mModel.endDate.monthDay = dayOfMonth;
   1090         mModel.endDate.normalize(false);
   1091         updateDialog();
   1092     }
   1093 
   1094     // Implements OnCheckedChangeListener interface
   1095     // Week repeat by day of week
   1096     @Override
   1097     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
   1098         int itemIdx = -1;
   1099         for (int i = 0; i < 7; i++) {
   1100             if (itemIdx == -1 && buttonView == mWeekByDayButtons[i]) {
   1101                 itemIdx = i;
   1102                 mModel.weeklyByDayOfWeek[i] = isChecked;
   1103             }
   1104         }
   1105         updateDialog();
   1106     }
   1107 
   1108     // Implements android.widget.RadioGroup.OnCheckedChangeListener interface
   1109     // Month repeat by radio buttons
   1110     @Override
   1111     public void onCheckedChanged(RadioGroup group, int checkedId) {
   1112         if (checkedId == R.id.repeatMonthlyByNthDayOfMonth) {
   1113             mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE;
   1114         } else if (checkedId == R.id.repeatMonthlyByNthDayOfTheWeek) {
   1115             mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK;
   1116         }
   1117         updateDialog();
   1118     }
   1119 
   1120     // Implements OnClickListener interface
   1121     // EndDate button
   1122     // Done button
   1123     @Override
   1124     public void onClick(View v) {
   1125         if (mEndDateTextView == v) {
   1126             if (mDatePickerDialog != null) {
   1127                 mDatePickerDialog.dismiss();
   1128             }
   1129             mDatePickerDialog = DatePickerDialog.newInstance(this, mModel.endDate.year,
   1130                     mModel.endDate.month, mModel.endDate.monthDay);
   1131             mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(getActivity()));
   1132             mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX);
   1133             mDatePickerDialog.show(getFragmentManager(), FRAG_TAG_DATE_PICKER);
   1134         } else if (mDone == v) {
   1135             String rrule;
   1136             if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
   1137                 rrule = null;
   1138             } else {
   1139                 copyModelToEventRecurrence(mModel, mRecurrence);
   1140                 rrule = mRecurrence.toString();
   1141             }
   1142             mRecurrenceSetListener.onRecurrenceSet(rrule);
   1143             dismiss();
   1144         }
   1145     }
   1146 
   1147     @Override
   1148     public void onActivityCreated(Bundle savedInstanceState) {
   1149         super.onActivityCreated(savedInstanceState);
   1150         mDatePickerDialog = (DatePickerDialog) getFragmentManager()
   1151                 .findFragmentByTag(FRAG_TAG_DATE_PICKER);
   1152         if (mDatePickerDialog != null) {
   1153             mDatePickerDialog.setOnDateSetListener(this);
   1154         }
   1155     }
   1156 
   1157     public interface OnRecurrenceSetListener {
   1158         void onRecurrenceSet(String rrule);
   1159     }
   1160 
   1161     public void setOnRecurrenceSetListener(OnRecurrenceSetListener l) {
   1162         mRecurrenceSetListener = l;
   1163     }
   1164 
   1165     private class EndSpinnerAdapter extends ArrayAdapter<CharSequence> {
   1166         final String END_DATE_MARKER = "%s";
   1167         final String END_COUNT_MARKER = "%d";
   1168 
   1169         private LayoutInflater mInflater;
   1170         private int mItemResourceId;
   1171         private int mTextResourceId;
   1172         private ArrayList<CharSequence> mStrings;
   1173         private String mEndDateString;
   1174         private boolean mUseFormStrings;
   1175 
   1176         /**
   1177          * @param context
   1178          * @param textViewResourceId
   1179          * @param objects
   1180          */
   1181         public EndSpinnerAdapter(Context context, ArrayList<CharSequence> strings,
   1182                 int itemResourceId, int textResourceId) {
   1183             super(context, itemResourceId, strings);
   1184             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   1185             mItemResourceId = itemResourceId;
   1186             mTextResourceId = textResourceId;
   1187             mStrings = strings;
   1188             mEndDateString = getResources().getString(R.string.recurrence_end_date);
   1189 
   1190             // If either date or count strings don't translate well, such that we aren't assured
   1191             // to have some text available to be placed in the spinner, then we'll have to use
   1192             // the more form-like versions of both strings instead.
   1193             int markerStart = mEndDateString.indexOf(END_DATE_MARKER);
   1194             if (markerStart <= 0) {
   1195                 // The date string does not have any text before the "%s" so we'll have to use the
   1196                 // more form-like strings instead.
   1197                 mUseFormStrings = true;
   1198             } else {
   1199                 String countEndStr = getResources().getQuantityString(
   1200                         R.plurals.recurrence_end_count, 1);
   1201                 markerStart = countEndStr.indexOf(END_COUNT_MARKER);
   1202                 if (markerStart <= 0) {
   1203                     // The count string does not have any text before the "%d" so we'll have to use
   1204                     // the more form-like strings instead.
   1205                     mUseFormStrings = true;
   1206                 }
   1207             }
   1208 
   1209             if (mUseFormStrings) {
   1210                 // We'll have to set the layout for the spinner to be weight=0 so it doesn't
   1211                 // take up too much space.
   1212                 mEndSpinner.setLayoutParams(
   1213                         new TableLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f));
   1214             }
   1215         }
   1216 
   1217         @Override
   1218         public View getView(int position, View convertView, ViewGroup parent) {
   1219             View v;
   1220             // Check if we can recycle the view
   1221             if (convertView == null) {
   1222                 v = mInflater.inflate(mTextResourceId, parent, false);
   1223             } else {
   1224                 v = convertView;
   1225             }
   1226 
   1227             TextView item = (TextView) v.findViewById(R.id.spinner_item);
   1228             int markerStart;
   1229             switch (position) {
   1230                 case RecurrenceModel.END_NEVER:
   1231                     item.setText(mStrings.get(RecurrenceModel.END_NEVER));
   1232                     break;
   1233                 case RecurrenceModel.END_BY_DATE:
   1234                     markerStart = mEndDateString.indexOf(END_DATE_MARKER);
   1235 
   1236                     if (markerStart != -1) {
   1237                         if (mUseFormStrings || markerStart == 0) {
   1238                             // If we get here, the translation of "Until" doesn't work correctly,
   1239                             // so we'll just set the whole "Until a date" string.
   1240                             item.setText(mEndDateLabel);
   1241                         } else {
   1242                             item.setText(mEndDateString.substring(0, markerStart).trim());
   1243                         }
   1244                     }
   1245                     break;
   1246                 case RecurrenceModel.END_BY_COUNT:
   1247                     String endString = mResources.getQuantityString(R.plurals.recurrence_end_count,
   1248                             mModel.endCount);
   1249                     markerStart = endString.indexOf(END_COUNT_MARKER);
   1250 
   1251                     if (markerStart != -1) {
   1252                         if (mUseFormStrings || markerStart == 0) {
   1253                             // If we get here, the translation of "For" doesn't work correctly,
   1254                             // so we'll just set the whole "For a number of events" string.
   1255                             item.setText(mEndCountLabel);
   1256                             // Also, we'll hide the " events" that would have been at the end.
   1257                             mPostEndCount.setVisibility(View.GONE);
   1258                             // Use this flag so the onItemSelected knows whether to show it later.
   1259                             mHidePostEndCount = true;
   1260                         } else {
   1261                             int postTextStart = markerStart + END_COUNT_MARKER.length();
   1262                             mPostEndCount.setText(endString.substring(postTextStart,
   1263                                     endString.length()).trim());
   1264                             // In case it's a recycled view that wasn't visible.
   1265                             if (mModel.end == RecurrenceModel.END_BY_COUNT) {
   1266                                 mPostEndCount.setVisibility(View.VISIBLE);
   1267                             }
   1268                             if (endString.charAt(markerStart - 1) == ' ') {
   1269                                 markerStart--;
   1270                             }
   1271                             item.setText(endString.substring(0, markerStart).trim());
   1272                         }
   1273                     }
   1274                     break;
   1275                 default:
   1276                     v = null;
   1277                     break;
   1278             }
   1279 
   1280             return v;
   1281         }
   1282 
   1283         @Override
   1284         public View getDropDownView(int position, View convertView, ViewGroup parent) {
   1285             View v;
   1286             // Check if we can recycle the view
   1287             if (convertView == null) {
   1288                 v = mInflater.inflate(mItemResourceId, parent, false);
   1289             } else {
   1290                 v = convertView;
   1291             }
   1292 
   1293             TextView item = (TextView) v.findViewById(R.id.spinner_item);
   1294             item.setText(mStrings.get(position));
   1295 
   1296             return v;
   1297         }
   1298     }
   1299 }
   1300