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
17package com.android.datetimepicker.date;
18
19import android.animation.ObjectAnimator;
20import android.app.Activity;
21import android.app.DialogFragment;
22import android.content.res.Resources;
23import android.os.Bundle;
24import android.text.format.DateUtils;
25import android.util.Log;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.View.OnClickListener;
29import android.view.ViewGroup;
30import android.view.Window;
31import android.view.WindowManager;
32import android.view.animation.AlphaAnimation;
33import android.view.animation.Animation;
34import android.widget.Button;
35import android.widget.LinearLayout;
36import android.widget.TextView;
37
38import com.android.datetimepicker.HapticFeedbackController;
39import com.android.datetimepicker.R;
40import com.android.datetimepicker.Utils;
41import com.android.datetimepicker.date.MonthAdapter.CalendarDay;
42
43import java.text.SimpleDateFormat;
44import java.util.Calendar;
45import java.util.HashSet;
46import java.util.Iterator;
47import java.util.Locale;
48
49/**
50 * Dialog allowing users to select a date.
51 *
52 * @deprecated Use {@link android.app.DatePickerDialog}.
53 */
54@Deprecated
55public class DatePickerDialog extends DialogFragment implements
56        OnClickListener, DatePickerController {
57
58    private static final String TAG = "DatePickerDialog";
59
60    private static final int UNINITIALIZED = -1;
61    private static final int MONTH_AND_DAY_VIEW = 0;
62    private static final int YEAR_VIEW = 1;
63
64    private static final String KEY_SELECTED_YEAR = "year";
65    private static final String KEY_SELECTED_MONTH = "month";
66    private static final String KEY_SELECTED_DAY = "day";
67    private static final String KEY_LIST_POSITION = "list_position";
68    private static final String KEY_WEEK_START = "week_start";
69    private static final String KEY_YEAR_START = "year_start";
70    private static final String KEY_YEAR_END = "year_end";
71    private static final String KEY_CURRENT_VIEW = "current_view";
72    private static final String KEY_LIST_POSITION_OFFSET = "list_position_offset";
73
74    private static final int DEFAULT_START_YEAR = 1900;
75    private static final int DEFAULT_END_YEAR = 2100;
76
77    private static final int ANIMATION_DURATION = 300;
78    private static final int ANIMATION_DELAY = 500;
79
80    private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
81    private static SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault());
82
83    private final Calendar mCalendar = Calendar.getInstance();
84    private OnDateSetListener mCallBack;
85    private HashSet<OnDateChangedListener> mListeners = new HashSet<OnDateChangedListener>();
86
87    private AccessibleDateAnimator mAnimator;
88
89    private TextView mDayOfWeekView;
90    private LinearLayout mMonthAndDayView;
91    private TextView mSelectedMonthTextView;
92    private TextView mSelectedDayTextView;
93    private TextView mYearView;
94    private DayPickerView mDayPickerView;
95    private YearPickerView mYearPickerView;
96    private Button mDoneButton;
97
98    private int mCurrentView = UNINITIALIZED;
99
100    private int mWeekStart = mCalendar.getFirstDayOfWeek();
101    private int mMinYear = DEFAULT_START_YEAR;
102    private int mMaxYear = DEFAULT_END_YEAR;
103    private Calendar mMinDate;
104    private Calendar mMaxDate;
105
106    private HapticFeedbackController mHapticFeedbackController;
107
108    private boolean mDelayAnimation = true;
109
110    // Accessibility strings.
111    private String mDayPickerDescription;
112    private String mSelectDay;
113    private String mYearPickerDescription;
114    private String mSelectYear;
115
116    /**
117     * The callback used to indicate the user is done filling in the date.
118     */
119    public interface OnDateSetListener {
120
121        /**
122         * @param view The view associated with this listener.
123         * @param year The year that was set.
124         * @param monthOfYear The month that was set (0-11) for compatibility
125         *            with {@link java.util.Calendar}.
126         * @param dayOfMonth The day of the month that was set.
127         */
128        void onDateSet(DatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth);
129    }
130
131    /**
132     * The callback used to notify other date picker components of a change in selected date.
133     */
134    public interface OnDateChangedListener {
135
136        public void onDateChanged();
137    }
138
139
140    public DatePickerDialog() {
141        // Empty constructor required for dialog fragment.
142    }
143
144    /**
145     * @param callBack How the parent is notified that the date is set.
146     * @param year The initial year of the dialog.
147     * @param monthOfYear The initial month of the dialog.
148     * @param dayOfMonth The initial day of the dialog.
149     */
150    public static DatePickerDialog newInstance(OnDateSetListener callBack, int year,
151            int monthOfYear,
152            int dayOfMonth) {
153        DatePickerDialog ret = new DatePickerDialog();
154        ret.initialize(callBack, year, monthOfYear, dayOfMonth);
155        return ret;
156    }
157
158    public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
159        mCallBack = callBack;
160        mCalendar.set(Calendar.YEAR, year);
161        mCalendar.set(Calendar.MONTH, monthOfYear);
162        mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
163    }
164
165    @Override
166    public void onCreate(Bundle savedInstanceState) {
167        super.onCreate(savedInstanceState);
168        final Activity activity = getActivity();
169        activity.getWindow().setSoftInputMode(
170                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
171        if (savedInstanceState != null) {
172            mCalendar.set(Calendar.YEAR, savedInstanceState.getInt(KEY_SELECTED_YEAR));
173            mCalendar.set(Calendar.MONTH, savedInstanceState.getInt(KEY_SELECTED_MONTH));
174            mCalendar.set(Calendar.DAY_OF_MONTH, savedInstanceState.getInt(KEY_SELECTED_DAY));
175        }
176    }
177
178    @Override
179    public void onSaveInstanceState(Bundle outState) {
180        super.onSaveInstanceState(outState);
181        outState.putInt(KEY_SELECTED_YEAR, mCalendar.get(Calendar.YEAR));
182        outState.putInt(KEY_SELECTED_MONTH, mCalendar.get(Calendar.MONTH));
183        outState.putInt(KEY_SELECTED_DAY, mCalendar.get(Calendar.DAY_OF_MONTH));
184        outState.putInt(KEY_WEEK_START, mWeekStart);
185        outState.putInt(KEY_YEAR_START, mMinYear);
186        outState.putInt(KEY_YEAR_END, mMaxYear);
187        outState.putInt(KEY_CURRENT_VIEW, mCurrentView);
188        int listPosition = -1;
189        if (mCurrentView == MONTH_AND_DAY_VIEW) {
190            listPosition = mDayPickerView.getMostVisiblePosition();
191        } else if (mCurrentView == YEAR_VIEW) {
192            listPosition = mYearPickerView.getFirstVisiblePosition();
193            outState.putInt(KEY_LIST_POSITION_OFFSET, mYearPickerView.getFirstPositionOffset());
194        }
195        outState.putInt(KEY_LIST_POSITION, listPosition);
196    }
197
198    @Override
199    public View onCreateView(LayoutInflater inflater, ViewGroup container,
200            Bundle savedInstanceState) {
201        Log.d(TAG, "onCreateView: ");
202        getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
203
204        View view = inflater.inflate(R.layout.date_picker_dialog, null);
205
206        mDayOfWeekView = (TextView) view.findViewById(R.id.date_picker_header);
207        mMonthAndDayView = (LinearLayout) view.findViewById(R.id.date_picker_month_and_day);
208        mMonthAndDayView.setOnClickListener(this);
209        mSelectedMonthTextView = (TextView) view.findViewById(R.id.date_picker_month);
210        mSelectedDayTextView = (TextView) view.findViewById(R.id.date_picker_day);
211        mYearView = (TextView) view.findViewById(R.id.date_picker_year);
212        mYearView.setOnClickListener(this);
213
214        int listPosition = -1;
215        int listPositionOffset = 0;
216        int currentView = MONTH_AND_DAY_VIEW;
217        if (savedInstanceState != null) {
218            mWeekStart = savedInstanceState.getInt(KEY_WEEK_START);
219            mMinYear = savedInstanceState.getInt(KEY_YEAR_START);
220            mMaxYear = savedInstanceState.getInt(KEY_YEAR_END);
221            currentView = savedInstanceState.getInt(KEY_CURRENT_VIEW);
222            listPosition = savedInstanceState.getInt(KEY_LIST_POSITION);
223            listPositionOffset = savedInstanceState.getInt(KEY_LIST_POSITION_OFFSET);
224        }
225
226        final Activity activity = getActivity();
227        mDayPickerView = new SimpleDayPickerView(activity, this);
228        mYearPickerView = new YearPickerView(activity, this);
229
230        Resources res = getResources();
231        mDayPickerDescription = res.getString(R.string.day_picker_description);
232        mSelectDay = res.getString(R.string.select_day);
233        mYearPickerDescription = res.getString(R.string.year_picker_description);
234        mSelectYear = res.getString(R.string.select_year);
235
236        mAnimator = (AccessibleDateAnimator) view.findViewById(R.id.animator);
237        mAnimator.addView(mDayPickerView);
238        mAnimator.addView(mYearPickerView);
239        mAnimator.setDateMillis(mCalendar.getTimeInMillis());
240        // TODO: Replace with animation decided upon by the design team.
241        Animation animation = new AlphaAnimation(0.0f, 1.0f);
242        animation.setDuration(ANIMATION_DURATION);
243        mAnimator.setInAnimation(animation);
244        // TODO: Replace with animation decided upon by the design team.
245        Animation animation2 = new AlphaAnimation(1.0f, 0.0f);
246        animation2.setDuration(ANIMATION_DURATION);
247        mAnimator.setOutAnimation(animation2);
248
249        mDoneButton = (Button) view.findViewById(R.id.done);
250        mDoneButton.setOnClickListener(new OnClickListener() {
251
252            @Override
253            public void onClick(View v) {
254                tryVibrate();
255                if (mCallBack != null) {
256                    mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR),
257                            mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
258                }
259                dismiss();
260            }
261        });
262
263        updateDisplay(false);
264        setCurrentView(currentView);
265
266        if (listPosition != -1) {
267            if (currentView == MONTH_AND_DAY_VIEW) {
268                mDayPickerView.postSetSelection(listPosition);
269            } else if (currentView == YEAR_VIEW) {
270                mYearPickerView.postSetSelectionFromTop(listPosition, listPositionOffset);
271            }
272        }
273
274        mHapticFeedbackController = new HapticFeedbackController(activity);
275        return view;
276    }
277
278    @Override
279    public void onResume() {
280        super.onResume();
281        mHapticFeedbackController.start();
282    }
283
284    @Override
285    public void onPause() {
286        super.onPause();
287        mHapticFeedbackController.stop();
288    }
289
290    private void setCurrentView(final int viewIndex) {
291        long millis = mCalendar.getTimeInMillis();
292
293        switch (viewIndex) {
294            case MONTH_AND_DAY_VIEW:
295                ObjectAnimator pulseAnimator = Utils.getPulseAnimator(mMonthAndDayView, 0.9f,
296                        1.05f);
297                if (mDelayAnimation) {
298                    pulseAnimator.setStartDelay(ANIMATION_DELAY);
299                    mDelayAnimation = false;
300                }
301                mDayPickerView.onDateChanged();
302                if (mCurrentView != viewIndex) {
303                    mMonthAndDayView.setSelected(true);
304                    mYearView.setSelected(false);
305                    mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW);
306                    mCurrentView = viewIndex;
307                }
308                pulseAnimator.start();
309
310                int flags = DateUtils.FORMAT_SHOW_DATE;
311                String dayString = DateUtils.formatDateTime(getActivity(), millis, flags);
312                mAnimator.setContentDescription(mDayPickerDescription+": "+dayString);
313                Utils.tryAccessibilityAnnounce(mAnimator, mSelectDay);
314                break;
315            case YEAR_VIEW:
316                pulseAnimator = Utils.getPulseAnimator(mYearView, 0.85f, 1.1f);
317                if (mDelayAnimation) {
318                    pulseAnimator.setStartDelay(ANIMATION_DELAY);
319                    mDelayAnimation = false;
320                }
321                mYearPickerView.onDateChanged();
322                if (mCurrentView != viewIndex) {
323                    mMonthAndDayView.setSelected(false);
324                    mYearView.setSelected(true);
325                    mAnimator.setDisplayedChild(YEAR_VIEW);
326                    mCurrentView = viewIndex;
327                }
328                pulseAnimator.start();
329
330                CharSequence yearString = YEAR_FORMAT.format(millis);
331                mAnimator.setContentDescription(mYearPickerDescription+": "+yearString);
332                Utils.tryAccessibilityAnnounce(mAnimator, mSelectYear);
333                break;
334        }
335    }
336
337    private void updateDisplay(boolean announce) {
338        if (mDayOfWeekView != null) {
339            mDayOfWeekView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
340                    Locale.getDefault()).toUpperCase(Locale.getDefault()));
341        }
342
343        mSelectedMonthTextView.setText(mCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT,
344                Locale.getDefault()).toUpperCase(Locale.getDefault()));
345        mSelectedDayTextView.setText(DAY_FORMAT.format(mCalendar.getTime()));
346        mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime()));
347
348        // Accessibility.
349        long millis = mCalendar.getTimeInMillis();
350        mAnimator.setDateMillis(millis);
351        int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR;
352        String monthAndDayText = DateUtils.formatDateTime(getActivity(), millis, flags);
353        mMonthAndDayView.setContentDescription(monthAndDayText);
354
355        if (announce) {
356            flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
357            String fullDateText = DateUtils.formatDateTime(getActivity(), millis, flags);
358            Utils.tryAccessibilityAnnounce(mAnimator, fullDateText);
359        }
360    }
361
362    public void setFirstDayOfWeek(int startOfWeek) {
363        if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) {
364            throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " +
365                    "Calendar.SATURDAY");
366        }
367        mWeekStart = startOfWeek;
368        if (mDayPickerView != null) {
369            mDayPickerView.onChange();
370        }
371    }
372
373    public void setYearRange(int startYear, int endYear) {
374        if (endYear <= startYear) {
375            throw new IllegalArgumentException("Year end must be larger than year start");
376        }
377        mMinYear = startYear;
378        mMaxYear = endYear;
379        if (mDayPickerView != null) {
380            mDayPickerView.onChange();
381        }
382    }
383
384    /**
385     * Sets the minimal date supported by this DatePicker. Dates before (but not including) the
386     * specified date will be disallowed from being selected.
387     * @param calendar a Calendar object set to the year, month, day desired as the mindate.
388     */
389    public void setMinDate(Calendar calendar) {
390        mMinDate = calendar;
391
392        if (mDayPickerView != null) {
393            mDayPickerView.onChange();
394        }
395    }
396
397    /**
398     * @return The minimal date supported by this DatePicker. Null if it has not been set.
399     */
400    @Override
401    public Calendar getMinDate() {
402        return mMinDate;
403    }
404
405    /**
406     * Sets the minimal date supported by this DatePicker. Dates after (but not including) the
407     * specified date will be disallowed from being selected.
408     * @param calendar a Calendar object set to the year, month, day desired as the maxdate.
409     */
410    public void setMaxDate(Calendar calendar) {
411        mMaxDate = calendar;
412
413        if (mDayPickerView != null) {
414            mDayPickerView.onChange();
415        }
416    }
417
418    /**
419     * @return The maximal date supported by this DatePicker. Null if it has not been set.
420     */
421    @Override
422    public Calendar getMaxDate() {
423        return mMaxDate;
424    }
425
426    public void setOnDateSetListener(OnDateSetListener listener) {
427        mCallBack = listener;
428    }
429
430    // If the newly selected month / year does not contain the currently selected day number,
431    // change the selected day number to the last day of the selected month or year.
432    //      e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
433    //      e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
434    private void adjustDayInMonthIfNeeded(int month, int year) {
435        int day = mCalendar.get(Calendar.DAY_OF_MONTH);
436        int daysInMonth = Utils.getDaysInMonth(month, year);
437        if (day > daysInMonth) {
438            mCalendar.set(Calendar.DAY_OF_MONTH, daysInMonth);
439        }
440    }
441
442    @Override
443    public void onClick(View v) {
444        tryVibrate();
445        if (v.getId() == R.id.date_picker_year) {
446            setCurrentView(YEAR_VIEW);
447        } else if (v.getId() == R.id.date_picker_month_and_day) {
448            setCurrentView(MONTH_AND_DAY_VIEW);
449        }
450    }
451
452    @Override
453    public void onYearSelected(int year) {
454        adjustDayInMonthIfNeeded(mCalendar.get(Calendar.MONTH), year);
455        mCalendar.set(Calendar.YEAR, year);
456        updatePickers();
457        setCurrentView(MONTH_AND_DAY_VIEW);
458        updateDisplay(true);
459    }
460
461    @Override
462    public void onDayOfMonthSelected(int year, int month, int day) {
463        mCalendar.set(Calendar.YEAR, year);
464        mCalendar.set(Calendar.MONTH, month);
465        mCalendar.set(Calendar.DAY_OF_MONTH, day);
466        updatePickers();
467        updateDisplay(true);
468    }
469
470    private void updatePickers() {
471        Iterator<OnDateChangedListener> iterator = mListeners.iterator();
472        while (iterator.hasNext()) {
473            iterator.next().onDateChanged();
474        }
475    }
476
477
478    @Override
479    public CalendarDay getSelectedDay() {
480        return new CalendarDay(mCalendar);
481    }
482
483    @Override
484    public int getMinYear() {
485        return mMinYear;
486    }
487
488    @Override
489    public int getMaxYear() {
490        return mMaxYear;
491    }
492
493    @Override
494    public int getFirstDayOfWeek() {
495        return mWeekStart;
496    }
497
498    @Override
499    public void registerOnDateChangedListener(OnDateChangedListener listener) {
500        mListeners.add(listener);
501    }
502
503    @Override
504    public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
505        mListeners.remove(listener);
506    }
507
508    @Override
509    public void tryVibrate() {
510        mHapticFeedbackController.tryVibrate();
511    }
512}
513