chromium/content/public/android/java/src/org/chromium/content/browser/picker/MultiFieldTimePickerDialog.java

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser.picker;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.NumberPicker;

import org.chromium.content.R;

import java.util.ArrayList;
import java.util.Locale;

/**
 * A time picker dialog with upto 5 number pickers left to right:
 *  hour, minute, second, milli, AM/PM.
 *
 * If is24hourFormat is true then AM/PM picker is not displayed and
 * hour range is 0..23. Otherwise hour range is 1..12.
 * The milli picker is not displayed if step >= SECOND_IN_MILLIS
 * The second picker is not displayed if step >= MINUTE_IN_MILLIS.
 */
public class MultiFieldTimePickerDialog extends AlertDialog implements OnClickListener {

    private final NumberPicker mHourSpinner;
    private final NumberPicker mMinuteSpinner;
    private final NumberPicker mSecSpinner;
    private final NumberPicker mMilliSpinner;
    private final NumberPicker mAmPmSpinner;
    private final OnMultiFieldTimeSetListener mListener;
    private final int mStep;
    private final int mBaseMilli;
    private final boolean mIs24hourFormat;

    /** Adds an onTimeSet() method. */
    public interface OnMultiFieldTimeSetListener {
        void onTimeSet(int hourOfDay, int minute, int second, int milli);
    }

    private static final int SECOND_IN_MILLIS = 1000;
    private static final int MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
    private static final int HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;

    public MultiFieldTimePickerDialog(
            Context context,
            int theme,
            int hour,
            int minute,
            int second,
            int milli,
            int min,
            int max,
            int step,
            boolean is24hourFormat,
            OnMultiFieldTimeSetListener listener) {
        super(context, theme);
        mListener = listener;
        mStep = step;
        mIs24hourFormat = is24hourFormat;

        if (min >= max) {
            min = 0;
            max = 24 * HOUR_IN_MILLIS - 1;
        }
        if (step < 0 || step >= 24 * HOUR_IN_MILLIS) {
            step = MINUTE_IN_MILLIS;
        }

        LayoutInflater inflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.multi_field_time_picker_dialog, null);
        setView(view);

        mHourSpinner = (NumberPicker) view.findViewById(R.id.hour);
        mMinuteSpinner = (NumberPicker) view.findViewById(R.id.minute);
        mSecSpinner = (NumberPicker) view.findViewById(R.id.second);
        mMilliSpinner = (NumberPicker) view.findViewById(R.id.milli);
        mAmPmSpinner = (NumberPicker) view.findViewById(R.id.ampm);

        int minHour = min / HOUR_IN_MILLIS;
        int maxHour = max / HOUR_IN_MILLIS;
        min -= minHour * HOUR_IN_MILLIS;
        max -= maxHour * HOUR_IN_MILLIS;

        if (minHour == maxHour) {
            mHourSpinner.setEnabled(false);
            hour = minHour;
        }

        if (is24hourFormat) {
            mAmPmSpinner.setVisibility(View.GONE);
        } else {
            int minAmPm = minHour / 12;
            int maxAmPm = maxHour / 12;
            int amPm = hour / 12;
            mAmPmSpinner.setMinValue(minAmPm);
            mAmPmSpinner.setMaxValue(maxAmPm);
            mAmPmSpinner.setDisplayedValues(
                    new String[] {
                        context.getString(R.string.time_picker_dialog_am),
                        context.getString(R.string.time_picker_dialog_pm)
                    });

            hour %= 12;
            if (hour == 0) {
                hour = 12;
            }
            if (minAmPm == maxAmPm) {
                mAmPmSpinner.setEnabled(false);
                amPm = minAmPm;

                minHour %= 12;
                maxHour %= 12;
                if (minHour == 0 && maxHour == 0) {
                    minHour = 12;
                    maxHour = 12;
                } else if (minHour == 0) {
                    minHour = maxHour;
                    maxHour = 12;
                } else if (maxHour == 0) {
                    maxHour = 12;
                }
            } else {
                minHour = 1;
                maxHour = 12;
            }
            mAmPmSpinner.setValue(amPm);
        }

        if (minHour == maxHour) {
            mHourSpinner.setEnabled(false);
        }
        mHourSpinner.setMinValue(minHour);
        mHourSpinner.setMaxValue(maxHour);
        mHourSpinner.setValue(hour);

        NumberFormatter twoDigitPaddingFormatter = new NumberFormatter("%02d");

        int minMinute = min / MINUTE_IN_MILLIS;
        int maxMinute = max / MINUTE_IN_MILLIS;
        min -= minMinute * MINUTE_IN_MILLIS;
        max -= maxMinute * MINUTE_IN_MILLIS;

        if (minHour == maxHour) {
            mMinuteSpinner.setMinValue(minMinute);
            mMinuteSpinner.setMaxValue(maxMinute);
            if (minMinute == maxMinute) {
                // Set this otherwise the box is empty until you stroke it.
                mMinuteSpinner.setDisplayedValues(
                        new String[] {twoDigitPaddingFormatter.format(minMinute)});
                mMinuteSpinner.setEnabled(false);
                minute = minMinute;
            }
        } else {
            mMinuteSpinner.setMinValue(0);
            mMinuteSpinner.setMaxValue(59);
        }

        mMinuteSpinner.setValue(minute);
        if (step % HOUR_IN_MILLIS == 0) {
            mMinuteSpinner.setEnabled(false);
            // TODO(tkent): We should set minutes value of WebDateTimeChooserParams::stepBase.
            mMinuteSpinner.setValue(minMinute);
        }

        mMinuteSpinner.setFormatter(twoDigitPaddingFormatter);

        if (step >= MINUTE_IN_MILLIS) {
            // Remove the ':' in front of the second spinner as well.
            view.findViewById(R.id.second_colon).setVisibility(View.GONE);
            mSecSpinner.setVisibility(View.GONE);
        }

        int minSecond = min / SECOND_IN_MILLIS;
        int maxSecond = max / SECOND_IN_MILLIS;
        min -= minSecond * SECOND_IN_MILLIS;
        max -= maxSecond * SECOND_IN_MILLIS;

        if (minHour == maxHour && minMinute == maxMinute) {
            mSecSpinner.setMinValue(minSecond);
            mSecSpinner.setMaxValue(maxSecond);
            if (minSecond == maxSecond) {
                // Set this otherwise the box is empty until you stroke it.
                mSecSpinner.setDisplayedValues(
                        new String[] {twoDigitPaddingFormatter.format(minSecond)});
                mSecSpinner.setEnabled(false);
                second = minSecond;
            }
        } else {
            mSecSpinner.setMinValue(0);
            mSecSpinner.setMaxValue(59);
        }

        mSecSpinner.setValue(second);
        mSecSpinner.setFormatter(twoDigitPaddingFormatter);

        if (step >= SECOND_IN_MILLIS) {
            // Remove the '.' in front of the milli spinner as well.
            view.findViewById(R.id.second_dot).setVisibility(View.GONE);
            mMilliSpinner.setVisibility(View.GONE);
        }

        // Round to the nearest step.
        milli = ((milli + step / 2) / step) * step;
        if (step == 1 || step == 10 || step == 100) {
            if (minHour == maxHour && minMinute == maxMinute && minSecond == maxSecond) {
                mMilliSpinner.setMinValue(min / step);
                mMilliSpinner.setMaxValue(max / step);

                if (min == max) {
                    mMilliSpinner.setEnabled(false);
                    milli = min;
                }
            } else {
                mMilliSpinner.setMinValue(0);
                mMilliSpinner.setMaxValue(999 / step);
            }

            if (step == 1) {
                mMilliSpinner.setFormatter(new NumberFormatter("%03d"));
            } else if (step == 10) {
                mMilliSpinner.setFormatter(new NumberFormatter("%02d"));
            } else if (step == 100) {
                mMilliSpinner.setFormatter(new NumberFormatter("%d"));
            }
            mMilliSpinner.setValue(milli / step);
            mBaseMilli = 0;
        } else if (step < SECOND_IN_MILLIS) {
            // Non-decimal step value.
            ArrayList<String> strValue = new ArrayList<String>();
            for (int i = min; i < max; i += step) {
                strValue.add(String.format(Locale.getDefault(), "%03d", i));
            }
            mMilliSpinner.setMinValue(0);
            mMilliSpinner.setMaxValue(strValue.size() - 1);
            mMilliSpinner.setValue((milli - min) / step);
            mMilliSpinner.setDisplayedValues(strValue.toArray(new String[strValue.size()]));
            mBaseMilli = min;
        } else {
            mBaseMilli = 0;
        }
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        notifyDateSet();
    }

    private void notifyDateSet() {
        int hour = getPickerValue(mHourSpinner);
        int minute = getPickerValue(mMinuteSpinner);
        int sec = getPickerValue(mSecSpinner);
        int milli = getPickerValue(mMilliSpinner) * mStep + mBaseMilli;
        if (!mIs24hourFormat) {
            int ampm = getPickerValue(mAmPmSpinner);
            if (hour == 12) {
                hour = 0;
            }
            hour += ampm * 12;
        }
        mListener.onTimeSet(hour, minute, sec, milli);
    }

    /**
     * Clear focus before retrieving so that values inserted with
     * keyboard are taken into account.
     */
    private int getPickerValue(NumberPicker picker) {
        picker.clearFocus();
        return picker.getValue();
    }

    private static class NumberFormatter implements NumberPicker.Formatter {
        private final String mFormat;

        NumberFormatter(String format) {
            mFormat = format;
        }

        @Override
        public String format(int value) {
            return String.format(Locale.getDefault(), mFormat, value);
        }
    }
}