diff options
Diffstat (limited to 'core')
28 files changed, 2451 insertions, 2041 deletions
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java index e6e55ee..8b70370 100644 --- a/core/java/android/app/DatePickerDialog.java +++ b/core/java/android/app/DatePickerDialog.java @@ -118,18 +118,12 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, } /** - * Sets the range of years in which dates can be selected. - * <p> - * Note: If the range is set to a value that does not include the currently - * selected date the value of the picker shown by this dialog will be - * updated to the closest date in the range. - * </p> + * Gets the {@link DatePicker} contained in this dialog. * - * @param startYear The start year of the range. - * @param endYear The end year of the range. + * @return The calendar view. */ - public void setRange(int startYear, int endYear) { - mDatePicker.setRange(startYear, endYear); + public DatePicker getDatePicker() { + return mDatePicker; } /** diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index f06f2cf..a920814 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -323,6 +323,15 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener int mState = INITIALIZING; + // Non-null if the fragment's view hierarchy is currently animating away, + // meaning we need to wait a bit on completely destroying it. This is the + // animation that is running. + Animator mAnimatingAway; + + // If mAnimatingAway != null, this is the state we should move to once the + // animation is done. + int mStateAfterAnimating; + // When instantiated from saved state, this is the saved state. Bundle mSavedFragmentState; SparseArray<Parcelable> mSavedViewState; @@ -1240,6 +1249,11 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener if (mView != null) { writer.print(prefix); writer.print("mView="); writer.println(mView); } + if (mAnimatingAway != null) { + writer.print(prefix); writer.print("mAnimatingAway="); writer.println(mAnimatingAway); + writer.print(prefix); writer.print("mStateAfterAnimating="); + writer.println(mStateAfterAnimating); + } if (mLoaderManager != null) { writer.print(prefix); writer.println("Loader Manager:"); mLoaderManager.dump(prefix + " ", fd, writer, args); diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index fe2ebed..3c98d67 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -38,6 +38,7 @@ import android.view.ViewGroup; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; /** * Interface for interacting with {@link Fragment} objects inside of an @@ -331,6 +332,7 @@ final class FragmentManagerImpl extends FragmentManager { boolean mNeedMenuInvalidate; boolean mStateSaved; + boolean mDestroyed; String mNoTransactionsBecause; // Temporary vars for state save and restore. @@ -473,23 +475,23 @@ final class FragmentManagerImpl extends FragmentManager { @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - if (mActive == null || mActive.size() <= 0) { - return; - } - - writer.print(prefix); writer.print("Active Fragments in "); - writer.print(Integer.toHexString(System.identityHashCode(this))); - writer.println(":"); - String innerPrefix = prefix + " "; - int N = mActive.size(); - for (int i=0; i<N; i++) { - Fragment f = mActive.get(i); - if (f != null) { - writer.print(prefix); writer.print(" #"); writer.print(i); - writer.print(": "); writer.println(f.toString()); - f.dump(innerPrefix, fd, writer, args); + int N; + if (mActive != null) { + N = mActive.size(); + if (N > 0) { + writer.print(prefix); writer.print("Active Fragments in "); + writer.print(Integer.toHexString(System.identityHashCode(this))); + writer.println(":"); + for (int i=0; i<N; i++) { + Fragment f = mActive.get(i); + writer.print(prefix); writer.print(" #"); writer.print(i); + writer.print(": "); writer.println(f); + if (f != null) { + f.dump(innerPrefix, fd, writer, args); + } + } } } @@ -505,6 +507,18 @@ final class FragmentManagerImpl extends FragmentManager { } } + if (mCreatedMenus != null) { + N = mCreatedMenus.size(); + if (N > 0) { + writer.print(prefix); writer.println("Fragments Created Menus:"); + for (int i=0; i<N; i++) { + Fragment f = mCreatedMenus.get(i); + writer.print(prefix); writer.print(" #"); writer.print(i); + writer.print(": "); writer.println(f.toString()); + } + } + } + if (mBackStack != null) { N = mBackStack.size(); if (N > 0) { @@ -517,6 +531,54 @@ final class FragmentManagerImpl extends FragmentManager { } } } + + synchronized (this) { + if (mBackStackIndices != null) { + N = mBackStackIndices.size(); + if (N > 0) { + writer.print(prefix); writer.println("Back Stack Indices:"); + for (int i=0; i<N; i++) { + BackStackRecord bs = mBackStackIndices.get(i); + writer.print(prefix); writer.print(" #"); writer.print(i); + writer.print(": "); writer.println(bs); + } + } + } + + if (mAvailBackStackIndices != null && mAvailBackStackIndices.size() > 0) { + writer.print(prefix); writer.print("mAvailBackStackIndices: "); + writer.println(Arrays.toString(mAvailBackStackIndices.toArray())); + } + } + + if (mPendingActions != null) { + N = mPendingActions.size(); + if (N > 0) { + writer.print(prefix); writer.println("Pending Actions:"); + for (int i=0; i<N; i++) { + Runnable r = mPendingActions.get(i); + writer.print(prefix); writer.print(" #"); writer.print(i); + writer.print(": "); writer.println(r); + } + } + } + + writer.print(prefix); writer.println("FragmentManager misc state:"); + writer.print(prefix); writer.print(" mCurState="); writer.print(mCurState); + writer.print(" mStateSaved="); writer.print(mStateSaved); + writer.print(" mDestroyed="); writer.println(mDestroyed); + if (mNeedMenuInvalidate) { + writer.print(prefix); writer.print(" mNeedMenuInvalidate="); + writer.println(mNeedMenuInvalidate); + } + if (mNoTransactionsBecause != null) { + writer.print(prefix); writer.print(" mNoTransactionsBecause="); + writer.println(mNoTransactionsBecause); + } + if (mAvailIndices != null && mAvailIndices.size() > 0) { + writer.print(prefix); writer.print(" mAvailIndices: "); + writer.println(Arrays.toString(mAvailIndices.toArray())); + } } Animator loadAnimator(Fragment fragment, int transit, boolean enter, @@ -569,6 +631,14 @@ final class FragmentManagerImpl extends FragmentManager { } if (f.mState < newState) { + if (f.mAnimatingAway != null) { + // The fragment is currently being animated... but! Now we + // want to move our state back up. Give up on waiting for the + // animation, move to whatever the final state should be once + // the animation is done, and then we can proceed from there. + f.mAnimatingAway = null; + moveToState(f, f.mStateAfterAnimating, 0, 0); + } switch (f.mState) { case Fragment.INITIALIZING: if (DEBUG) Log.v(TAG, "moveto CREATED: " + f); @@ -716,18 +786,26 @@ final class FragmentManagerImpl extends FragmentManager { } if (f.mView != null && f.mContainer != null) { Animator anim = null; - if (mCurState > Fragment.INITIALIZING) { + if (mCurState > Fragment.INITIALIZING && !mDestroyed) { anim = loadAnimator(f, transit, false, transitionStyle); } if (anim != null) { final ViewGroup container = f.mContainer; final View view = f.mView; + final Fragment fragment = f; container.startViewTransition(view); + f.mAnimatingAway = anim; + f.mStateAfterAnimating = newState; anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator anim) { container.endViewTransition(view); + if (fragment.mAnimatingAway != null) { + fragment.mAnimatingAway = null; + moveToState(fragment, fragment.mStateAfterAnimating, + 0, 0); + } } }); anim.setTarget(f.mView); @@ -741,25 +819,46 @@ final class FragmentManagerImpl extends FragmentManager { } case Fragment.CREATED: if (newState < Fragment.CREATED) { - if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f); - if (!f.mRetaining) { + if (mDestroyed) { + if (f.mAnimatingAway != null) { + // The fragment's containing activity is + // being destroyed, but this fragment is + // currently animating away. Stop the + // animation right now -- it is not needed, + // and we can't wait any more on destroying + // the fragment. + Animator anim = f.mAnimatingAway; + f.mAnimatingAway = null; + anim.cancel(); + } + } + if (f.mAnimatingAway != null) { + // We are waiting for the fragment's view to finish + // animating away. Just make a note of the state + // the fragment now should move to once the animation + // is done. + f.mStateAfterAnimating = newState; + } else { + if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f); + if (!f.mRetaining) { + f.mCalled = false; + f.onDestroy(); + if (!f.mCalled) { + throw new SuperNotCalledException("Fragment " + f + + " did not call through to super.onDestroy()"); + } + } + f.mCalled = false; - f.onDestroy(); + f.onDetach(); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onDestroy()"); + + " did not call through to super.onDetach()"); } + f.mImmediateActivity = null; + f.mActivity = null; + f.mFragmentManager = null; } - - f.mCalled = false; - f.onDetach(); - if (!f.mCalled) { - throw new SuperNotCalledException("Fragment " + f - + " did not call through to super.onDetach()"); - } - f.mImmediateActivity = null; - f.mActivity = null; - f.mFragmentManager = null; } } } @@ -873,9 +972,19 @@ final class FragmentManagerImpl extends FragmentManager { transitionStyle); if (anim != null) { anim.setTarget(fragment.mView); + // Delay the actual hide operation until the animation finishes, otherwise + // the fragment will just immediately disappear + final Fragment finalFragment = fragment; + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finalFragment.mView.setVisibility(View.GONE); + } + }); anim.start(); + } else { + fragment.mView.setVisibility(View.GONE); } - fragment.mView.setVisibility(View.GONE); } if (fragment.mAdded && fragment.mHasMenu) { mNeedMenuInvalidate = true; @@ -1442,6 +1551,7 @@ final class FragmentManagerImpl extends FragmentManager { } public void dispatchDestroy() { + mDestroyed = true; moveToState(Fragment.INITIALIZING, false); mActivity = null; } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index da1aac4..d034229 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -201,6 +201,7 @@ public abstract class ContentResolver { } catch (RemoteException e) { return null; } catch (java.lang.Exception e) { + Log.w(TAG, "Failed to get type for: " + url + " (" + e.getMessage() + ")"); return null; } finally { releaseProvider(provider); @@ -216,6 +217,9 @@ public abstract class ContentResolver { return type; } catch (RemoteException e) { return null; + } catch (java.lang.Exception e) { + Log.w(TAG, "Failed to get type for: " + url + " (" + e.getMessage() + ")"); + return null; } } diff --git a/core/java/android/hardware/UsbManager.java b/core/java/android/hardware/UsbManager.java index 1003bf9..6022b12 100644 --- a/core/java/android/hardware/UsbManager.java +++ b/core/java/android/hardware/UsbManager.java @@ -105,6 +105,14 @@ public class UsbManager { */ public static final String USB_FUNCTION_DISABLED = "disabled"; + public static final int getDeviceId(String name) { + return native_get_device_id(name); + } + + public static final String getDeviceName(int id) { + return native_get_device_name(id); + } + private static File getFunctionEnableFile(String function) { return new File("/sys/class/usb_composite/" + function + "/enable"); } @@ -130,4 +138,7 @@ public class UsbManager { return false; } } + + private static native int native_get_device_id(String name); + private static native String native_get_device_name(int id); } diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index 0ad80dd..f750122 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -65,7 +65,9 @@ public final class Proxy { * that either the default connection or its proxy has changed. * The intent will have the following extra value:</p> * <ul> - * <li><em>EXTRA_PROXY_INFO</em> - The ProxyProperties for the proxy + * <li><em>EXTRA_PROXY_INFO</em> - The ProxyProperties for the proxy. Non-null, + * though if the proxy is undefined the host string + * will be empty. * </ul> * * <p class="note">This is a protected intent that can only be sent by the system diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java index 103fd94..2c48a04 100644 --- a/core/java/android/net/http/RequestHandle.java +++ b/core/java/android/net/http/RequestHandle.java @@ -308,7 +308,7 @@ public class RequestHandle { String A2 = mMethod + ":" + mUrl; // because we do not preemptively send authorization headers, nc is always 1 - String nc = "000001"; + String nc = "00000001"; String cnonce = computeCnonce(); String digest = computeDigest(A1, A2, nonce, QOP, nc, cnonce); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e8792ff..c435c43 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -381,6 +381,11 @@ public interface WindowManager extends ViewManager { */ public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17; + /** + * Window type: (mouse) pointer + * @hide + */ + public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18; /** * End of types of system windows. diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 5b24c50..0a165e8 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -4485,6 +4485,11 @@ public class WebView extends AbsoluteLayout + ", " + event + ", unicode=" + event.getUnicodeChar()); } + // don't implement accelerator keys here; defer to host application + if (event.isCtrlPressed()) { + return false; + } + if (mNativeClass == 0) { return false; } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index ab98763..6d3f227 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -512,6 +512,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private AbsListView.PerformClick mPerformClick; /** + * Delayed action for touch mode. + */ + private Runnable mTouchModeReset; + + /** * This view is in transcript mode -- it shows the bottom of the list when the data * changes */ @@ -2322,6 +2327,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mFlingStrictSpan.finish(); mFlingStrictSpan = null; } + + if (mFlingRunnable != null) { + removeCallbacks(mFlingRunnable); + } + + if (mPositionScroller != null) { + removeCallbacks(mPositionScroller); + } + + if (mClearScrollingCache != null) { + removeCallbacks(mClearScrollingCache); + } + + if (mPerformClick != null) { + removeCallbacks(mPerformClick); + } + + if (mTouchModeReset != null) { + removeCallbacks(mTouchModeReset); + mTouchModeReset = null; + } } @Override @@ -3020,7 +3046,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te ((TransitionDrawable) d).resetTransition(); } } - postDelayed(new Runnable() { + if (mTouchModeReset != null) { + removeCallbacks(mTouchModeReset); + } + mTouchModeReset = new Runnable() { + @Override public void run() { mTouchMode = TOUCH_MODE_REST; child.setPressed(false); @@ -3029,7 +3059,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te post(performClick); } } - }, ViewConfiguration.getPressedStateDuration()); + }; + postDelayed(mTouchModeReset, + ViewConfiguration.getPressedStateDuration()); } else { mTouchMode = TOUCH_MODE_REST; updateSelectorState(); diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java new file mode 100644 index 0000000..7ef61a8 --- /dev/null +++ b/core/java/android/widget/CalendarView.java @@ -0,0 +1,1398 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import com.android.internal.R; + +import android.annotation.Widget; +import android.app.Service; +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView.OnScrollListener; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +import libcore.icu.LocaleData; + +/** + * This class is a calendar widget for displaying and selecting dates. The range + * of dates supported by this calendar is configurable. A user can select a date + * by taping on it and can scroll and fling the calendar to a desired date. + * + * @attr ref android.R.styleable#CalendarView_showWeekNumber + * @attr ref android.R.styleable#CalendarView_firstDayOfWeek + * @attr ref android.R.styleable#CalendarView_minDate + * @attr ref android.R.styleable#CalendarView_maxDate + * @attr ref android.R.styleable#CalendarView_shownWeekCount + * @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor + * @attr ref android.R.styleable#CalendarView_focusedMonthDateColor + * @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor + * @attr ref android.R.styleable#CalendarView_weekNumberColor + * @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor + * @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar + * @attr ref android.R.styleable#CalendarView_weekDayTextAppearance + * @attr ref android.R.styleable#CalendarView_dateTextAppearance + */ +@Widget +public class CalendarView extends FrameLayout { + + /** + * Tag for logging. + */ + private static final String LOG_TAG = CalendarView.class.getSimpleName(); + + /** + * Default value whether to show week number. + */ + private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true; + + /** + * The number of milliseconds in a day.e + */ + private static final long MILLIS_IN_DAY = 86400000L; + + /** + * The number of day in a week. + */ + private static final int DAYS_PER_WEEK = 7; + + /** + * The number of milliseconds in a week. + */ + private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; + + /** + * Affects when the month selection will change while scrolling upe + */ + private static final int SCROLL_HYST_WEEKS = 2; + + /** + * How long the GoTo fling animation should last. + */ + private static final int GOTO_SCROLL_DURATION = 1000; + + /** + * The duration of the adjustment upon a user scroll in milliseconds. + */ + private static final int ADJUSTMENT_SCROLL_DURATION = 500; + + /** + * How long to wait after receiving an onScrollStateChanged notification + * before acting on it. + */ + private static final int SCROLL_CHANGE_DELAY = 40; + + /** + * String for formatting the month name in the title text view. + */ + private static final String FORMAT_MONTH_NAME = "MMMM, yyyy"; + + /** + * String for parsing dates. + */ + private static final String DATE_FORMAT = "MM/dd/yyyy"; + + /** + * The default minimal date. + */ + private static final String DEFAULT_MIN_DATE = "01/01/1900"; + + /** + * The default maximal date. + */ + private static final String DEFAULT_MAX_DATE = "01/01/2100"; + + private static final int DEFAULT_SHOWN_WEEK_COUNT = 6; + + private static final int DEFAULT_DATE_TEXT_SIZE = 14; + + private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6; + + private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12; + + private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2; + + private static final int UNSCALED_BOTTOM_BUFFER = 20; + + private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1; + + private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1; + + private final int mWeekSeperatorLineWidth; + + private final int mDateTextSize; + + private final Drawable mSelectedDateVerticalBar; + + private final int mSelectedDateVerticalBarWidth; + + private final int mSelectedWeekBackgroundColor; + + private final int mFocusedMonthDateColor; + + private final int mUnfocusedMonthDateColor; + + private final int mWeekSeparatorLineColor; + + private final int mWeekNumberColor; + + /** + * The top offset of the weeks list. + */ + private int mListScrollTopOffset = 2; + + /** + * The visible height of a week view. + */ + private int mWeekMinVisibleHeight = 12; + + /** + * The visible height of a week view. + */ + private int mBottomBuffer = 20; + + /** + * The number of shown weeks. + */ + private int mShownWeekCount; + + /** + * Flag whether to show the week number. + */ + private boolean mShowWeekNumber; + + /** + * The number of day per week to be shown. + */ + private int mDaysPerWeek = 7; + + /** + * The friction of the week list while flinging. + */ + private float mFriction = .05f; + + /** + * Scale for adjusting velocity of the week list while flinging. + */ + private float mVelocityScale = 0.333f; + + /** + * The adapter for the weeks list. + */ + private WeeksAdapter mAdapter; + + /** + * The weeks list. + */ + private ListView mListView; + + /** + * The name of the month to display. + */ + private TextView mMonthName; + + /** + * The header with week day names. + */ + private ViewGroup mDayNamesHeader; + + /** + * Cached labels for the week names header. + */ + private String[] mDayLabels; + + /** + * Temporary instance to avoid multiple instantiations. + */ + private Calendar mTempDate = Calendar.getInstance(); + + /** + * The first day of the week. + */ + private int mFirstDayOfWeek; + + /** + * The first day of the focused month. + */ + private Calendar mFirstDayOfMonth = Calendar.getInstance(); + + /** + * Which month should be displayed/highlighted [0-11]. + */ + private int mCurrentMonthDisplayed; + + /** + * Used for tracking during a scroll. + */ + private long mPreviousScrollPosition; + + /** + * Used for tracking which direction the view is scrolling. + */ + private boolean mIsScrollingUp = false; + + /** + * The previous scroll state of the weeks ListView. + */ + private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + /** + * The current scroll state of the weeks ListView. + */ + private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + /** + * Listener for changes in the selected day. + */ + private OnDateChangeListener mOnDateChangeListener; + + /** + * Command for adjusting the position after a scroll/fling. + */ + private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); + + /** + * The start date of the range supported by this picker. + */ + private Calendar mMinDate = Calendar.getInstance(); + + /** + * The end date of the range supported by this picker. + */ + private Calendar mMaxDate = Calendar.getInstance(); + + /** + * Date format for parsing dates. + */ + private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); + + /** + * The callback used to indicate the user changes the date. + */ + public interface OnDateChangeListener { + + /** + * Called upon change of the selected day. + * + * @param view The view associated with this listener. + * @param year The year that was set. + * @param month The month that was set [0-11]. + * @param dayOfMonth The day of the month that was set. + */ + public void onSelectedDayChange(CalendarView view, int year, int month, int dayOfMonth); + } + + public CalendarView(Context context) { + this(context, null); + } + + public CalendarView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CalendarView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, 0); + + TypedValue calendarViewStyle = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.calendarViewStyle, calendarViewStyle, true); + TypedArray attributesArray = context.obtainStyledAttributes(calendarViewStyle.resourceId, + R.styleable.CalendarView); + mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber, + DEFAULT_SHOW_WEEK_NUMBER); + mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek, + LocaleData.get(Locale.getDefault()).firstDayOfWeek); + String minDate = attributesArray.getString(R.styleable.CalendarView_minDate); + if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) { + parseDate(DEFAULT_MIN_DATE, mMinDate); + } + String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate); + if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) { + parseDate(DEFAULT_MAX_DATE, mMaxDate); + } + mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount, + DEFAULT_SHOWN_WEEK_COUNT); + mSelectedWeekBackgroundColor = attributesArray.getColor( + R.styleable.CalendarView_selectedWeekBackgroundColor, 0); + mFocusedMonthDateColor = attributesArray.getColor( + R.styleable.CalendarView_focusedMonthDateColor, 0); + mUnfocusedMonthDateColor = attributesArray.getColor( + R.styleable.CalendarView_unfocusedMonthDateColor, 0); + mWeekSeparatorLineColor = attributesArray.getColor( + R.styleable.CalendarView_weekSeparatorLineColor, 0); + mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0); + mSelectedDateVerticalBar = attributesArray.getDrawable( + R.styleable.CalendarView_selectedDateVerticalBar); + + int dateTextAppearanceResId= attributesArray.getResourceId( + R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small); + TypedArray dateTextAppearance = context.obtainStyledAttributes(dateTextAppearanceResId, + com.android.internal.R.styleable.TextAppearance); + mDateTextSize = dateTextAppearance.getDimensionPixelSize( + R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE); + + int weekDayTextAppearanceResId = attributesArray.getResourceId( + R.styleable.CalendarView_weekDayTextAppearance, + DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); + attributesArray.recycle(); + + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics); + mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics); + mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_BOTTOM_BUFFER, displayMetrics); + mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics); + mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics); + + LayoutInflater layoutInflater = (LayoutInflater) mContext + .getSystemService(Service.LAYOUT_INFLATER_SERVICE); + View content = layoutInflater.inflate(R.layout.calendar_view, null, false); + addView(content); + + mListView = (ListView) findViewById(R.id.list); + mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); + mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); + + setUpHeader(weekDayTextAppearanceResId); + setUpListView(); + setUpAdapter(); + + // go to today now + mTempDate.setTimeInMillis(System.currentTimeMillis()); + goTo(mTempDate, false, true, true); + invalidate(); + } + + @Override + public void setEnabled(boolean enabled) { + mListView.setEnabled(enabled); + } + + @Override + public boolean isEnabled() { + return mListView.isEnabled(); + } + + /** + * Gets the minimal date supported by this {@link CalendarView} in milliseconds + * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time + * zone. + * <p> + * Note: The default minimal date is 01/01/1900. + * <p> + * + * @return The minimal supported date. + */ + public long getMinDate() { + return mMinDate.getTimeInMillis(); + } + + /** + * Sets the minimal date supported by this {@link CalendarView} in milliseconds + * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time + * zone. + * + * @param minDate The minimal supported date. + */ + public void setMinDate(long minDate) { + mTempDate.setTimeInMillis(minDate); + if (isSameDate(mTempDate, mMinDate)) { + return; + } + mMinDate.setTimeInMillis(minDate); + // reinitialize the adapter since its range depends on min date + mAdapter.init(); + Calendar date = mAdapter.mSelectedDate; + if (date.before(mMinDate)) { + setDate(mMinDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } + } + + /** + * Gets the maximal date supported by this {@link CalendarView} in milliseconds + * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time + * zone. + * <p> + * Note: The default maximal date is 01/01/2100. + * <p> + * + * @return The maximal supported date. + */ + public long getMaxDate() { + return mMaxDate.getTimeInMillis(); + } + + /** + * Sets the maximal date supported by this {@link CalendarView} in milliseconds + * since January 1, 1970 00:00:00 in {@link TimeZone#getDefault()} time + * zone. + * + * @param maxDate The maximal supported date. + */ + public void setMaxDate(long maxDate) { + mTempDate.setTimeInMillis(maxDate); + if (isSameDate(mTempDate, mMaxDate)) { + return; + } + mMaxDate.setTimeInMillis(maxDate); + // reinitialize the adapter since its range depends on max date + mAdapter.init(); + Calendar date = mAdapter.mSelectedDate; + if (date.after(mMaxDate)) { + setDate(mMaxDate.getTimeInMillis()); + } else { + // we go to the current date to force the ListView to query its + // adapter for the shown views since we have changed the adapter + // range and the base from which the later calculates item indices + // note that calling setDate will not work since the date is the same + goTo(date, false, true, false); + } + } + + /** + * Sets whether to show the week number. + * + * @param showWeekNumber True to show the week number. + */ + public void setShowWeekNumber(boolean showWeekNumber) { + if (mShowWeekNumber == showWeekNumber) { + return; + } + mShowWeekNumber = showWeekNumber; + mAdapter.notifyDataSetChanged(); + setUpHeader(DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); + } + + /** + * Gets whether to show the week number. + * + * @return True if showing the week number. + */ + public boolean getShowWeekNumber() { + return mShowWeekNumber; + } + + /** + * Gets the first day of week. + * + * @return The first day of the week conforming to the {@link CalendarView} + * APIs. + * @see Calendar#MONDAY + * @see Calendar#TUESDAY + * @see Calendar#WEDNESDAY + * @see Calendar#THURSDAY + * @see Calendar#FRIDAY + * @see Calendar#SATURDAY + * @see Calendar#SUNDAY + */ + public int getFirstDayOfWeek() { + return mFirstDayOfWeek; + } + + /** + * Sets the first day of week. + * + * @param firstDayOfWeek The first day of the week conforming to the + * {@link CalendarView} APIs. + * @see Calendar#MONDAY + * @see Calendar#TUESDAY + * @see Calendar#WEDNESDAY + * @see Calendar#THURSDAY + * @see Calendar#FRIDAY + * @see Calendar#SATURDAY + * @see Calendar#SUNDAY + */ + public void setFirstDayOfWeek(int firstDayOfWeek) { + if (mFirstDayOfWeek == firstDayOfWeek) { + return; + } + mFirstDayOfWeek = firstDayOfWeek; + mAdapter.init(); + mAdapter.notifyDataSetChanged(); + setUpHeader(DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID); + } + + /** + * Sets the listener to be notified upon selected date change. + * + * @param listener The listener to be notified. + */ + public void setOnDateChangeListener(OnDateChangeListener listener) { + mOnDateChangeListener = listener; + } + + /** + * Gets the selected date in milliseconds since January 1, 1970 00:00:00 in + * {@link TimeZone#getDefault()} time zone. + * + * @return The selected date. + */ + public long getDate() { + return mAdapter.mSelectedDate.getTimeInMillis(); + } + + /** + * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in + * {@link TimeZone#getDefault()} time zone. + * + * @param date The selected date. + * + * @throws IllegalArgumentException of the provided date is before the + * minimal or after the maximal date. + * + * @see #setDate(long, boolean, boolean) + * @see #setMinDate(long) + * @see #setMaxDate(long) + */ + public void setDate(long date) { + setDate(date, false, false); + } + + /** + * Sets the selected date in milliseconds since January 1, 1970 00:00:00 in + * {@link TimeZone#getDefault()} time zone. + * + * @param date The date. + * @param animate Whether to animate the scroll to the current date. + * @param center Whether to center the current date even if it is already visible. + * + * @throws IllegalArgumentException of the provided date is before the + * minimal or after the maximal date. + * + * @see #setMinDate(long) + * @see #setMaxDate(long) + */ + public void setDate(long date, boolean animate, boolean center) { + mTempDate.setTimeInMillis(date); + if (isSameDate(mTempDate, mAdapter.mSelectedDate)) { + return; + } + goTo(mTempDate, animate, true, center); + } + + /** + * @return True if the <code>firstDate</code> is the same as the <code> + * secondDate</code>. + */ + private boolean isSameDate(Calendar firstDate, Calendar secondDate) { + return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR) + && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR)); + } + + /** + * Creates a new adapter if necessary and sets up its parameters. + */ + private void setUpAdapter() { + if (mAdapter == null) { + mAdapter = new WeeksAdapter(getContext()); + mAdapter.registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + if (mOnDateChangeListener != null) { + Calendar selectedDay = mAdapter.getSelectedDay(); + mOnDateChangeListener.onSelectedDayChange(CalendarView.this, + selectedDay.get(Calendar.YEAR), + selectedDay.get(Calendar.MONTH), + selectedDay.get(Calendar.DAY_OF_MONTH)); + } + } + }); + mListView.setAdapter(mAdapter); + } + + // refresh the view with the new parameters + mAdapter.notifyDataSetChanged(); + } + + /** + * Sets up the strings to be used by the header. + */ + private void setUpHeader(int weekDayTextAppearanceResId) { + mDayLabels = new String[mDaysPerWeek]; + for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { + int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i; + mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, + DateUtils.LENGTH_SHORTEST); + } + + TextView label = (TextView) mDayNamesHeader.getChildAt(0); + if (mShowWeekNumber) { + label.setVisibility(View.VISIBLE); + } else { + label.setVisibility(View.GONE); + } + for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { + label = (TextView) mDayNamesHeader.getChildAt(i); + if (weekDayTextAppearanceResId > -1) { + label.setTextAppearance(mContext, weekDayTextAppearanceResId); + } + if (i < mDaysPerWeek + 1) { + label.setText(mDayLabels[i - 1]); + label.setVisibility(View.VISIBLE); + } else { + label.setVisibility(View.GONE); + } + } + mDayNamesHeader.invalidate(); + } + + /** + * Sets all the required fields for the list view. + */ + private void setUpListView() { + // Configure the listview + mListView.setDivider(null); + mListView.setItemsCanFocus(true); + mListView.setVerticalScrollBarEnabled(false); + mListView.setOnScrollListener(new OnScrollListener() { + public void onScrollStateChanged(AbsListView view, int scrollState) { + CalendarView.this.onScrollStateChanged(view, scrollState); + } + + public void onScroll( + AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + CalendarView.this.onScroll(view, firstVisibleItem, visibleItemCount, + totalItemCount); + } + }); + // Make the scrolling behavior nicer + mListView.setFriction(mFriction); + mListView.setVelocityScale(mVelocityScale); + } + + /** + * This moves to the specified time in the view. If the time is not already + * in range it will move the list so that the first of the month containing + * the time is at the top of the view. If the new time is already in view + * the list will not be scrolled unless forceScroll is true. This time may + * optionally be highlighted as selected as well. + * + * @param date The time to move to. + * @param animate Whether to scroll to the given time or just redraw at the + * new location. + * @param setSelected Whether to set the given time as selected. + * @param forceScroll Whether to recenter even if the time is already + * visible. + * + * @throws IllegalArgumentException of the provided date is before the + * range start of after the range end. + */ + private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) { + if (date.before(mMinDate) || date.after(mMaxDate)) { + throw new IllegalArgumentException("Time not between " + mMinDate.getTime() + + " and " + mMaxDate.getTime()); + } + // Find the first and last entirely visible weeks + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + View firstChild = mListView.getChildAt(0); + if (firstChild != null && firstChild.getTop() < 0) { + firstFullyVisiblePosition++; + } + int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; + if (firstChild != null && firstChild.getTop() > mBottomBuffer) { + lastFullyVisiblePosition--; + } + if (setSelected) { + mAdapter.setSelectedDay(date); + } + // Get the week we're going to + int position = getWeeksSinceMinDate(date); + + // Check if the selected day is now outside of our visible range + // and if so scroll to the month that contains it + if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition + || forceScroll) { + mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); + mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); + + setMonthDisplayed(mFirstDayOfMonth); + position = getWeeksSinceMinDate(mFirstDayOfMonth); + + mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; + if (animate) { + mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset, + GOTO_SCROLL_DURATION); + } else { + mListView.setSelectionFromTop(position, mListScrollTopOffset); + // Perform any after scroll operations that are needed + onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); + } + } else if (setSelected) { + // Otherwise just set the selection + setMonthDisplayed(date); + } + } + + /** + * Parses the given <code>date</code> and in case of success sets + * the result to the <code>outDate</code>. + * + * @return True if the date was parsed. + */ + private boolean parseDate(String date, Calendar outDate) { + try { + outDate.setTime(mDateFormat.parse(date)); + return true; + } catch (ParseException e) { + Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); + return false; + } + } + + /** + * Called when a <code>view</code> transitions to a new <code>scrollState + * </code>. + */ + private void onScrollStateChanged(AbsListView view, int scrollState) { + mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); + } + + /** + * Updates the title and selected month if the <code>view</code> has moved to a new + * month. + */ + private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + WeekView child = (WeekView) view.getChildAt(0); + if (child == null) { + return; + } + + // Figure out where we are + long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); + + // If we have moved since our last call update the direction + if (currScroll < mPreviousScrollPosition) { + mIsScrollingUp = true; + } else if (currScroll > mPreviousScrollPosition) { + mIsScrollingUp = false; + } else { + return; + } + + // Use some hysteresis for checking which month to highlight. This + // causes the month to transition when two full weeks of a month are + // visible when scrolling up, and when the first day in a month reaches + // the top of the screen when scrolling down. + int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; + if (mIsScrollingUp) { + child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); + } else if (offset != 0) { + child = (WeekView) view.getChildAt(offset); + } + + // Find out which month we're moving into + int month; + if (mIsScrollingUp) { + month = child.getMonthOfFirstWeekDay(); + } else { + month = child.getMonthOfLastWeekDay(); + } + + // And how it relates to our current highlighted month + int monthDiff; + if (mCurrentMonthDisplayed == 11 && month == 0) { + monthDiff = 1; + } else if (mCurrentMonthDisplayed == 0 && month == 11) { + monthDiff = -1; + } else { + monthDiff = month - mCurrentMonthDisplayed; + } + + // Only switch months if we're scrolling away from the currently + // selected month + if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { + Calendar firstDay = child.getFirstDay(); + if (mIsScrollingUp) { + firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); + } else { + firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); + } + setMonthDisplayed(firstDay); + } + mPreviousScrollPosition = currScroll; + mPreviousScrollState = mCurrentScrollState; + } + + /** + * Sets the month displayed at the top of this view based on time. Override + * to add custom events when the title is changed. + * + * @param calendar A day in the new focus month. + */ + private void setMonthDisplayed(Calendar calendar) { + mMonthName.setText(DateFormat.format(FORMAT_MONTH_NAME, calendar)); + mMonthName.invalidate(); + mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); + mAdapter.setFocusMonth(mCurrentMonthDisplayed); + // TODO Send Accessibility Event + } + + /** + * @return Returns the number of weeks between the current <code>date</code> + * and the <code>mMinDate</code>. + */ + private int getWeeksSinceMinDate(Calendar date) { + if (date.before(mMinDate)) { + throw new IllegalArgumentException("fromDate: " + mMinDate.getTime() + + " does not precede toDate: " + date.getTime()); + } + int fromDateDayOfWeek = mMinDate.get(Calendar.DAY_OF_WEEK); + long diff = (fromDateDayOfWeek - mFirstDayOfWeek) * MILLIS_IN_DAY; + long refDay = mMinDate.getTimeInMillis() - diff; + return (int) ((date.getTimeInMillis() - refDay) / MILLIS_IN_WEEK); + } + + /** + * Command responsible for acting upon scroll state changes. + */ + private class ScrollStateRunnable implements Runnable { + private AbsListView mView; + + private int mNewState; + + /** + * Sets up the runnable with a short delay in case the scroll state + * immediately changes again. + * + * @param view The list view that changed state + * @param scrollState The new state it changed to + */ + public void doScrollStateChange(AbsListView view, int scrollState) { + mView = view; + mNewState = scrollState; + removeCallbacks(this); + postDelayed(this, SCROLL_CHANGE_DELAY); + } + + public void run() { + mCurrentScrollState = mNewState; + // Fix the position after a scroll or a fling ends + if (mNewState == OnScrollListener.SCROLL_STATE_IDLE + && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { + View child = mView.getChildAt(0); + if (child == null) { + // The view is no longer visible, just return + return; + } + int dist = child.getBottom() - mListScrollTopOffset; + if (dist > mListScrollTopOffset) { + if (mIsScrollingUp) { + mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION); + } else { + mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); + } + } + } + mPreviousScrollState = mNewState; + } + } + + /** + * <p> + * This is a specialized adapter for creating a list of weeks with + * selectable days. It can be configured to display the week number, start + * the week on a given day, show a reduced number of days, or display an + * arbitrary number of weeks at a time. + * </p> + */ + private class WeeksAdapter extends BaseAdapter implements OnTouchListener { + + private int mSelectedWeek; + + private GestureDetector mGestureDetector; + + private int mFocusedMonth; + + private final Calendar mSelectedDate = Calendar.getInstance(); + + private int mTotalWeekCount; + + public WeeksAdapter(Context context) { + mContext = context; + mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); + init(); + } + + /** + * Set up the gesture detector and selected time + */ + private void init() { + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mTotalWeekCount = getWeeksSinceMinDate(mMaxDate); + if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek + || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) { + mTotalWeekCount++; + } + } + + /** + * Updates the selected day and related parameters. + * + * @param selectedDay The time to highlight + */ + public void setSelectedDay(Calendar selectedDay) { + if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR) + && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) { + return; + } + mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis()); + mSelectedWeek = getWeeksSinceMinDate(mSelectedDate); + mFocusedMonth = mSelectedDate.get(Calendar.MONTH); + notifyDataSetChanged(); + } + + /** + * @return The selected day of month. + */ + public Calendar getSelectedDay() { + return mSelectedDate; + } + + @Override + public int getCount() { + return mTotalWeekCount; + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + WeekView weekView = null; + if (convertView != null) { + weekView = (WeekView) convertView; + } else { + weekView = new WeekView(mContext); + android.widget.AbsListView.LayoutParams params = + new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + weekView.setLayoutParams(params); + weekView.setClickable(true); + weekView.setOnTouchListener(this); + } + + int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get( + Calendar.DAY_OF_WEEK) : -1; + weekView.init(position, selectedWeekDay, mFocusedMonth); + + return weekView; + } + + /** + * Changes which month is in focus and updates the view. + * + * @param month The month to show as in focus [0-11] + */ + public void setFocusMonth(int month) { + if (mFocusedMonth == month) { + return; + } + mFocusedMonth = month; + notifyDataSetChanged(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) { + WeekView weekView = (WeekView) v; + weekView.getDayFromLocation(event.getX(), mTempDate); + // it is possible that the touched day is outside the valid range + // we draw whole weeks but range end can fall not on the week end + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + return true; + } + onDateTapped(mTempDate); + return true; + } + return false; + } + + /** + * Maintains the same hour/min/sec but moves the day to the tapped day. + * + * @param day The day that was tapped + */ + private void onDateTapped(Calendar day) { + setSelectedDay(day); + setMonthDisplayed(day); + } + + /** + * This is here so we can identify single tap events and set the + * selected day correctly + */ + class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + } + } + + /** + * <p> + * This is a dynamic view for drawing a single week. It can be configured to + * display the week number, start the week on a given day, or show a reduced + * number of days. It is intended for use as a single view within a + * ListView. See {@link WeeksAdapter} for usage. + * </p> + */ + private class WeekView extends View { + + private final Rect mTempRect = new Rect(); + + private final Paint mDrawPaint = new Paint(); + + private final Paint mMonthNumDrawPaint = new Paint(); + + // Cache the number strings so we don't have to recompute them each time + private String[] mDayNumbers; + + // Quick lookup for checking which days are in the focus month + private boolean[] mFocusDay; + + // The first day displayed by this item + private Calendar mFirstDay; + + // The month of the first day in this week + private int mMonthOfFirstWeekDay = -1; + + // The month of the last day in this week + private int mLastWeekDayMonth = -1; + + // The position of this week, equivalent to weeks since the week of Jan + // 1st, 1900 + private int mWeek = -1; + + // Quick reference to the width of this view, matches parent + private int mWidth; + + // The height this view should draw at in pixels, set by height param + private int mHeight; + + // If this view contains the selected day + private boolean mHasSelectedDay = false; + + // Which day is selected [0-6] or -1 if no day is selected + private int mSelectedDay = -1; + + // The number of days + a spot for week number if it is displayed + private int mNumCells; + + // The left edge of the selected day + private int mSelectedLeft = -1; + + // The right edge of the selected day + private int mSelectedRight = -1; + + public WeekView(Context context) { + super(context); + + mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView + .getPaddingBottom()) / mShownWeekCount; + + // Sets up any standard paints that will be used + setPaintProperties(); + } + + /** + * Initializes this week view. + * + * @param weekNumber The number of the week this view represents. The + * week number is a zero based index of the weeks since + * {@link CalendarView#getMinDate()}. + * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no + * selected day. + * @param focusedMonth The month that is currently in focus i.e. + * highlighted. + */ + public void init(int weekNumber, int selectedWeekDay, int focusedMonth) { + mSelectedDay = selectedWeekDay; + mHasSelectedDay = mSelectedDay != -1; + mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek; + mWeek = weekNumber; + mTempDate.setTimeInMillis(mMinDate.getTimeInMillis()); + mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek); + mTempDate.setFirstDayOfWeek(mFirstDayOfWeek); + + // Allocate space for caching the day numbers and focus values + mDayNumbers = new String[mNumCells]; + mFocusDay = new boolean[mNumCells]; + + // If we're showing the week number calculate it based on Monday + int i = 0; + if (mShowWeekNumber) { + mDayNumbers[0] = Integer.toString(mTempDate.get(Calendar.WEEK_OF_YEAR)); + i++; + } + + // Now adjust our starting day based on the start day of the week + int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK); + mTempDate.add(Calendar.DAY_OF_MONTH, diff); + + mFirstDay = (Calendar) mTempDate.clone(); + mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH); + + for (; i < mNumCells; i++) { + mFocusDay[i] = (mTempDate.get(Calendar.MONTH) == focusedMonth); + // do not draw dates outside the valid range to avoid user confusion + if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) { + mDayNumbers[i] = ""; + } else { + mDayNumbers[i] = Integer.toString(mTempDate.get(Calendar.DAY_OF_MONTH)); + } + mTempDate.add(Calendar.DAY_OF_MONTH, 1); + } + // We do one extra add at the end of the loop, if that pushed us to + // new month undo it + if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) { + mTempDate.add(Calendar.DAY_OF_MONTH, -1); + } + mLastWeekDayMonth = mTempDate.get(Calendar.MONTH); + + updateSelectionPositions(); + } + + /** + * Sets up the text and style properties for painting. + */ + private void setPaintProperties() { + mDrawPaint.setFakeBoldText(false); + mDrawPaint.setAntiAlias(true); + mDrawPaint.setTextSize(mDateTextSize); + mDrawPaint.setStyle(Style.FILL); + + mMonthNumDrawPaint.setFakeBoldText(true); + mMonthNumDrawPaint.setAntiAlias(true); + mMonthNumDrawPaint.setTextSize(mDateTextSize); + mMonthNumDrawPaint.setColor(mFocusedMonthDateColor); + mMonthNumDrawPaint.setStyle(Style.FILL); + mMonthNumDrawPaint.setTextAlign(Align.CENTER); + } + + /** + * Returns the month of the first day in this week. + * + * @return The month the first day of this view is in. + */ + public int getMonthOfFirstWeekDay() { + return mMonthOfFirstWeekDay; + } + + /** + * Returns the month of the last day in this week + * + * @return The month the last day of this view is in + */ + public int getMonthOfLastWeekDay() { + return mLastWeekDayMonth; + } + + /** + * Returns the first day in this view. + * + * @return The first day in the view. + */ + public Calendar getFirstDay() { + return mFirstDay; + } + + /** + * Calculates the day that the given x position is in, accounting for + * week number. Returns a Time referencing that day or null if + * + * @param x The x position of the touch eventy + */ + public void getDayFromLocation(float x, Calendar outCalendar) { + int dayStart = mShowWeekNumber ? mWidth / mNumCells : 0; + if (x < dayStart || x > mWidth) { + outCalendar.clear(); + return; + } + // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels + int dayPosition = (int) ((x - dayStart) * mDaysPerWeek + / (mWidth - dayStart)); + outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); + outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); + } + + @Override + protected void onDraw(Canvas canvas) { + drawBackground(canvas); + drawWeekNumbers(canvas); + drawWeekSeparators(canvas); + drawSelectedDateVerticalBars(canvas); + } + + /** + * This draws the selection highlight if a day is selected in this week. + * + * @param canvas The canvas to draw on + */ + private void drawBackground(Canvas canvas) { + if (!mHasSelectedDay) { + return; + } + mDrawPaint.setColor(mSelectedWeekBackgroundColor); + + mTempRect.top = mWeekSeperatorLineWidth; + mTempRect.bottom = mHeight; + mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0; + mTempRect.right = mSelectedLeft - 2; + canvas.drawRect(mTempRect, mDrawPaint); + + mTempRect.left = mSelectedRight + 3; + mTempRect.right = mWidth; + canvas.drawRect(mTempRect, mDrawPaint); + } + + /** + * Draws the week and month day numbers for this week. + * + * @param canvas The canvas to draw on + */ + private void drawWeekNumbers(Canvas canvas) { + float textHeight = mDrawPaint.getTextSize(); + int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth; + int nDays = mNumCells; + + mDrawPaint.setTextAlign(Align.CENTER); + int i = 0; + int divisor = 2 * nDays; + if (mShowWeekNumber) { + mDrawPaint.setColor(mWeekNumberColor); + int x = mWidth / divisor; + canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); + i++; + } + for (; i < nDays; i++) { + mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor + : mUnfocusedMonthDateColor); + int x = (2 * i + 1) * mWidth / divisor; + canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); + } + } + + /** + * Draws a horizontal line for separating the weeks. + * + * @param canvas The canvas to draw on. + */ + private void drawWeekSeparators(Canvas canvas) { + // If it is the topmost fully visible child do not draw separator line + int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); + if (mListView.getChildAt(0).getTop() < 0) { + firstFullyVisiblePosition++; + } + if (firstFullyVisiblePosition == mWeek) { + return; + } + mDrawPaint.setColor(mWeekSeparatorLineColor); + mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth); + float x = mShowWeekNumber ? mWidth / mNumCells : 0; + canvas.drawLine(x, 0, mWidth, 0, mDrawPaint); + } + + /** + * Draws the selected date bars if this week has a selected day. + * + * @param canvas The canvas to draw on + */ + private void drawSelectedDateVerticalBars(Canvas canvas) { + if (!mHasSelectedDay) { + return; + } + mSelectedDateVerticalBar.setBounds(mSelectedLeft - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight); + mSelectedDateVerticalBar.draw(canvas); + mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2, + mWeekSeperatorLineWidth, + mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight); + mSelectedDateVerticalBar.draw(canvas); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mWidth = w; + updateSelectionPositions(); + } + + /** + * This calculates the positions for the selected day lines. + */ + private void updateSelectionPositions() { + if (mHasSelectedDay) { + int selectedPosition = mSelectedDay - mFirstDayOfWeek; + if (selectedPosition < 0) { + selectedPosition += 7; + } + if (mShowWeekNumber) { + selectedPosition++; + } + mSelectedLeft = selectedPosition * mWidth / mNumCells; + mSelectedRight = (selectedPosition + 1) * mWidth / mNumCells; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); + } + } +} diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 668490d..493b881 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -23,56 +23,96 @@ import android.content.Context; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; +import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; -import android.widget.NumberPicker.OnChangeListener; +import android.widget.NumberPicker.OnValueChangedListener; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; +import java.util.TimeZone; /** - * A view for selecting a month / year / day based on a calendar like layout. + * This class is a widget for selecting a date. The date can be selected by a + * year, month, and day spinners or a {@link CalendarView}. The set of spinners + * and the calendar view are automatically synchronized. The client can + * customize whether only the spinners, or only the calendar view, or both to be + * displayed. Also the minimal and maximal date from which dates to be selected + * can be customized. * <p> * See the <a href="{@docRoot} * resources/tutorials/views/hello-datepicker.html">Date Picker tutorial</a>. * </p> + * <p> * For a dialog using this view, see {@link android.app.DatePickerDialog}. + * </p> + * + * @attr ref android.R.styleable#DatePicker_startYear + * @attr ref android.R.styleable#DatePicker_endYear + * @attr ref android.R.styleable#DatePicker_maxDate + * @attr ref android.R.styleable#DatePicker_minDate + * @attr ref android.R.styleable#DatePicker_spinnersShown + * @attr ref android.R.styleable#DatePicker_calendarViewShown */ @Widget public class DatePicker extends FrameLayout { + private static final String LOG_TAG = DatePicker.class.getSimpleName(); + + private static final String DATE_FORMAT = "MM/dd/yyyy"; + private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; - private final NumberPicker mDayPicker; + private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true; + + private static final boolean DEFAULT_SPINNERS_SHOWN = true; - private final NumberPicker mMonthPicker; + private final NumberPicker mDaySpinner; - private final NumberPicker mYearPicker; + private final LinearLayout mSpinners; - private final DayPicker mMiniMonthDayPicker; + private final NumberPicker mMonthSpinner; + + private final NumberPicker mYearSpinner; + + private final CalendarView mCalendarView; private OnDateChangedListener mOnDateChangedListener; private Locale mMonthLocale; - private final Calendar mTempCalendar = Calendar.getInstance(); + private final Calendar mTempDate = Calendar.getInstance(); - private final int mNumberOfMonths = mTempCalendar.getActualMaximum(Calendar.MONTH) + 1; + private final int mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1; private final String[] mShortMonths = new String[mNumberOfMonths]; + private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT); + + private final Calendar mMinDate = Calendar.getInstance(); + + private final Calendar mMaxDate = Calendar.getInstance(); + + private final Calendar mCurrentDate = Calendar.getInstance(); + + private boolean mIsEnabled; + /** - * The callback used to indicate the user changes the date. + * The callback used to indicate the user changes\d the date. */ public interface OnDateChangedListener { /** + * Called upon a date change. + * * @param view The view associated with this listener. * @param year The year that was set. * @param monthOfYear The month that was set (0-11) for compatibility @@ -93,103 +133,227 @@ public class DatePicker extends FrameLayout { public DatePicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker); + boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown, + DEFAULT_SPINNERS_SHOWN); + boolean calendarViewShown = attributesArray.getBoolean( + R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN); + int startYear = attributesArray + .getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); + int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); + String minDate = attributesArray.getString(R.styleable.DatePicker_minDate); + String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate); + attributesArray.recycle(); + LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.date_picker, this, true); - OnChangeListener onChangeListener = new OnChangeListener() { - public void onChange(NumberPicker picker, int oldVal, int newVal) { - updateDateUnchecked(mYearPicker.getCurrent(), mMonthPicker.getCurrent(), - mDayPicker.getCurrent()); + OnValueChangedListener onChangeListener = new OnValueChangedListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + updateDate(mYearSpinner.getValue(), mMonthSpinner.getValue(), mDaySpinner + .getValue()); } }; - // mini-month day-picker - mMiniMonthDayPicker = (DayPicker) findViewById(R.id.mini_month_day_picker); - mMiniMonthDayPicker.setOnDateChangeListener(new DayPicker.OnSelectedDayChangeListener() { - public void onSelectedDayChange(DayPicker view, int year, int month, int monthDay) { - updateDateUnchecked(year, month, monthDay); + mSpinners = (LinearLayout) findViewById(R.id.pickers); + + // calendar view day-picker + mCalendarView = (CalendarView) findViewById(R.id.calendar_view); + mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() { + public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { + updateDate(year, month, monthDay); } }); // day - mDayPicker = (NumberPicker) findViewById(R.id.day); - mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); - mDayPicker.setOnLongPressUpdateInterval(100); - mDayPicker.setOnChangeListener(onChangeListener); + mDaySpinner = (NumberPicker) findViewById(R.id.day); + mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + mDaySpinner.setOnLongPressUpdateInterval(100); + mDaySpinner.setOnValueChangedListener(onChangeListener); // month - mMonthPicker = (NumberPicker) findViewById(R.id.month); - mMonthPicker.setRange(0, mNumberOfMonths - 1, getShortMonths()); - mMonthPicker.setOnLongPressUpdateInterval(200); - mMonthPicker.setOnChangeListener(onChangeListener); + mMonthSpinner = (NumberPicker) findViewById(R.id.month); + mMonthSpinner.setMinValue(0); + mMonthSpinner.setMaxValue(mNumberOfMonths - 1); + mMonthSpinner.setDisplayedValues(getShortMonths()); + mMonthSpinner.setOnLongPressUpdateInterval(200); + mMonthSpinner.setOnValueChangedListener(onChangeListener); // year - mYearPicker = (NumberPicker) findViewById(R.id.year); - mYearPicker.setOnLongPressUpdateInterval(100); - mYearPicker.setOnChangeListener(onChangeListener); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker); - int startYear = a.getInt(R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); - int endYear = a.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); - setRange(startYear, endYear); - a.recycle(); - - // initialize to current date - mTempCalendar.setTimeInMillis(System.currentTimeMillis()); - init(mTempCalendar.get(Calendar.YEAR), mTempCalendar.get(Calendar.MONTH), - mTempCalendar.get(Calendar.DAY_OF_MONTH), null); - - // re-order the number pickers to match the current date format - reorderPickers(); + mYearSpinner = (NumberPicker) findViewById(R.id.year); + mYearSpinner.setOnLongPressUpdateInterval(100); + mYearSpinner.setOnValueChangedListener(onChangeListener); + + // show only what the user required but make sure we + // show something and the spinners have higher priority + if (!spinnersShown && !calendarViewShown) { + setSpinnersShown(true); + } else { + setSpinnersShown(spinnersShown); + setCalendarViewShown(calendarViewShown); + + // set the min date giving priority of the minDate over startYear + mTempDate.clear(); + if (!TextUtils.isEmpty(minDate)) { + if (!parseDate(minDate, mTempDate)) { + mTempDate.set(startYear, 0, 1); + } + } else { + mTempDate.set(startYear, 0, 1); + } + mMinDate.clear(); + setMinDate(mTempDate.getTimeInMillis()); + + // set the max date giving priority of the minDate over startYear + mTempDate.clear(); + if (!TextUtils.isEmpty(maxDate)) { + if (!parseDate(maxDate, mTempDate)) { + mTempDate.set(endYear, 11, 31); + } + } else { + mTempDate.set(endYear, 11, 31); + } + mMaxDate.clear(); + setMaxDate(mTempDate.getTimeInMillis()); + + // initialize to current date + mCurrentDate.setTimeInMillis(System.currentTimeMillis()); + init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate + .get(Calendar.DAY_OF_MONTH), null); + } + + // re-order the number spinners to match the current date format + reorderSpinners(); + } + + /** + * Gets the minimal date supported by this {@link DatePicker} in + * milliseconds since January 1, 1970 00:00:00 in + * {@link TimeZone#getDefault()} time zone. + * <p> + * Note: The default minimal date is 01/01/1900. + * <p> + * + * @return The minimal supported date. + */ + public long getMinDate() { + return mCalendarView.getMinDate(); + } + + /** + * Sets the minimal date supported by this {@link NumberPicker} in + * milliseconds since January 1, 1970 00:00:00 in + * {@link TimeZone#getDefault()} time zone. + * + * @param minDate The minimal supported date. + */ + public void setMinDate(long minDate) { + mTempDate.setTimeInMillis(minDate); + if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR) + && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) { + return; + } + mMinDate.setTimeInMillis(minDate); + mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); + mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); + mCalendarView.setMinDate(minDate); + updateSpinners(mYearSpinner.getValue(), mMonthSpinner.getValue(), mDaySpinner.getValue()); } /** - * Sets the range of years in which dates can be selected. + * Gets the maximal date supported by this {@link DatePicker} in + * milliseconds since January 1, 1970 00:00:00 in + * {@link TimeZone#getDefault()} time zone. + * <p> + * Note: The default maximal date is 12/31/2100. * <p> - * Note: If the range is set to a value that does not include the currently - * selected date the value of this picker will be updated to the closest - * date in the range. - * </p> * - * @param startYear The start year of the range. - * @param endYear The end year of the range. + * @return The maximal supported date. */ - public void setRange(int startYear, int endYear) { - // set ranges of the widgets - mYearPicker.setRange(startYear, endYear); - mTempCalendar.clear(); - Calendar startRangeDate = (Calendar) mTempCalendar.clone(); - startRangeDate.set(startYear, 0, 1); - Calendar endRangeDate = (Calendar) mTempCalendar.clone(); - endRangeDate.set(endYear, 11, 31); - mMiniMonthDayPicker.setRange(startRangeDate, endRangeDate); - - // update state if current date is outside of the range - mTempCalendar.set(Calendar.YEAR, getYear()); - mTempCalendar.set(Calendar.MONTH, getMonth()); - mTempCalendar.set(Calendar.DAY_OF_MONTH, getDayOfMonth()); - if (mTempCalendar.before(startRangeDate)) { - updateDate(startRangeDate.get(Calendar.YEAR), startRangeDate.get(Calendar.MONTH), - startRangeDate.get(Calendar.DAY_OF_MONTH)); - } else if (mTempCalendar.after(endRangeDate)) { - updateDate(endRangeDate.get(Calendar.YEAR), endRangeDate.get(Calendar.MONTH), - endRangeDate.get(Calendar.DAY_OF_MONTH)); + public long getMaxDate() { + return mCalendarView.getMaxDate(); + } + + /** + * Sets the maximal date supported by this {@link DatePicker} in + * milliseconds since January 1, 1970 00:00:00 in + * {@link TimeZone#getDefault()} time zone. + * + * @param maxDate The maximal supported date. + */ + public void setMaxDate(long maxDate) { + mTempDate.setTimeInMillis(maxDate); + if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR) + && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) { + return; } + mMaxDate.setTimeInMillis(maxDate); + mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR)); + mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR)); + mCalendarView.setMaxDate(maxDate); + updateSpinners(mYearSpinner.getValue(), mMonthSpinner.getValue(), mDaySpinner.getValue()); } @Override public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; + } super.setEnabled(enabled); - mDayPicker.setEnabled(enabled); - mMonthPicker.setEnabled(enabled); - mYearPicker.setEnabled(enabled); - mMiniMonthDayPicker.setEnabled(enabled); + mDaySpinner.setEnabled(enabled); + mMonthSpinner.setEnabled(enabled); + mYearSpinner.setEnabled(enabled); + mCalendarView.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * Gets whether the {@link CalendarView} is shown. + * + * @return True if the calendar view is shown. + */ + public boolean getCalendarViewShown() { + return mCalendarView.isShown(); + } + + /** + * Sets whether the {@link CalendarView} is shown. + * + * @param shown True if the calendar view is to be shown. + */ + public void setCalendarViewShown(boolean shown) { + mCalendarView.setVisibility(shown ? VISIBLE : GONE); + } + + /** + * Gets whether the spinners are shown. + * + * @return True if the spinners are shown. + */ + public boolean getSpinnersShown() { + return mSpinners.isShown(); + } + + /** + * Sets whether the spinners are shown. + * + * @param shown True if the spinners are to be shown. + */ + public void setSpinnersShown(boolean shown) { + mSpinners.setVisibility(shown ? VISIBLE : GONE); } /** - * Reorders the pickers according to the date format in the current locale. + * Reorders the spinners according to the date format in the current + * {@link Locale}. */ - private void reorderPickers() { + private void reorderSpinners() { java.text.DateFormat format; String order; @@ -214,10 +378,10 @@ public class DatePicker extends FrameLayout { } /* - * Remove the 3 pickers from their parent and then add them back in the + * Remove the 3 spinners from their parent and then add them back in the * required order. */ - LinearLayout parent = (LinearLayout) findViewById(R.id.pickers); + LinearLayout parent = mSpinners; parent.removeAllViews(); boolean quoted = false; @@ -232,13 +396,13 @@ public class DatePicker extends FrameLayout { if (!quoted) { if (c == DateFormat.DATE && !didDay) { - parent.addView(mDayPicker); + parent.addView(mDaySpinner); didDay = true; } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) { - parent.addView(mMonthPicker); + parent.addView(mMonthSpinner); didMonth = true; } else if (c == DateFormat.YEAR && !didYear) { - parent.addView(mYearPicker); + parent.addView(mYearSpinner); didYear = true; } } @@ -246,13 +410,13 @@ public class DatePicker extends FrameLayout { // Shouldn't happen, but just in case. if (!didMonth) { - parent.addView(mMonthPicker); + parent.addView(mMonthSpinner); } if (!didDay) { - parent.addView(mDayPicker); + parent.addView(mDaySpinner); } if (!didYear) { - parent.addView(mYearPicker); + parent.addView(mYearSpinner); } } @@ -264,10 +428,12 @@ public class DatePicker extends FrameLayout { * @param dayOfMonth The day of the month. */ public void updateDate(int year, int month, int dayOfMonth) { - if (mYearPicker.getCurrent() != year - || mDayPicker.getCurrent() != dayOfMonth - || mMonthPicker.getCurrent() != month) { - updateDateUnchecked(year, month, dayOfMonth); + if (mCurrentDate.get(Calendar.YEAR) != year + || mCurrentDate.get(Calendar.MONTH) != dayOfMonth + || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month) { + updateSpinners(year, month, dayOfMonth); + updateCalendarView(); + notifyDateChanged(); } } @@ -280,20 +446,20 @@ public class DatePicker extends FrameLayout { @Override protected Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); - return new SavedState(superState, mYearPicker.getCurrent(), mMonthPicker.getCurrent(), - mDayPicker.getCurrent()); + return new SavedState(superState, mYearSpinner.getValue(), mMonthSpinner.getValue(), + mDaySpinner.getValue()); } @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); - updatePickers(ss.mYear, ss.mMonth, ss.mDay); + updateSpinners(ss.mYear, ss.mMonth, ss.mDay); } /** * Initialize the state. If the provided values designate an inconsistent - * date the values are normalized before updating the pickers. + * date the values are normalized before updating the spinners. * * @param year The initial year. * @param monthOfYear The initial month <strong>starting from zero</strong>. @@ -308,16 +474,19 @@ public class DatePicker extends FrameLayout { } /** - * Updates the current date. + * Parses the given <code>date</code> and in case of success sets the result + * to the <code>outDate</code>. * - * @param year The year. - * @param month The month which is <strong>starting from zero</strong>. - * @param dayOfMonth The day of the month. + * @return True if the date was parsed. */ - private void updateDateUnchecked(int year, int month, int dayOfMonth) { - updatePickers(year, month, dayOfMonth); - updateMiniMonth(); - notifyDateChanged(); + private boolean parseDate(String date, Calendar outDate) { + try { + outDate.setTime(mDateFormat.parse(date)); + return true; + } catch (ParseException e) { + Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT); + return false; + } } /** @@ -338,33 +507,80 @@ public class DatePicker extends FrameLayout { } /** - * Updates the pickers with the given <code>year</code>, <code>month</code>, - * and <code>dayOfMonth</code>. If the provided values designate an inconsistent - * date the values are normalized before updating the pickers. + * Updates the spinners with the given <code>year</code>, <code>month</code> + * , and <code>dayOfMonth</code>. If the provided values designate an + * inconsistent date the values are normalized before updating the spinners. + */ + private void updateSpinners(int year, int month, int dayOfMonth) { + mCurrentDate.set(Calendar.YEAR, year); + int deltaMonths = getDelataMonth(month); + mCurrentDate.add(Calendar.MONTH, deltaMonths); + int deltaDays = getDelataDayOfMonth(dayOfMonth); + mCurrentDate.add(Calendar.DAY_OF_MONTH, deltaDays); + + if (mCurrentDate.before(mMinDate)) { + mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis()); + } else if (mCurrentDate.after(mMaxDate)) { + mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis()); + } + + mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR)); + mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH)); + mDaySpinner.setMinValue(1); + mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH)); + mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH)); + } + + /** + * @return The delta days of moth from the current date and the given + * <code>dayOfMonth</code>. */ - private void updatePickers(int year, int month, int dayOfMonth) { - // larger fields are not updated and the day is adjusted without wrapping - mTempCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth); - mTempCalendar.roll(Calendar.MONTH, month - mTempCalendar.get(Calendar.MONTH)); - mTempCalendar.roll(Calendar.YEAR, year - mTempCalendar.get(Calendar.YEAR)); - - mYearPicker.setCurrent(mTempCalendar.get(Calendar.YEAR)); - mMonthPicker.setCurrent(mTempCalendar.get(Calendar.MONTH)); - mDayPicker.setRange(1, mTempCalendar.getActualMaximum(Calendar.DAY_OF_MONTH)); - mDayPicker.setCurrent(mTempCalendar.get(Calendar.DAY_OF_MONTH)); + private int getDelataDayOfMonth(int dayOfMonth) { + int prevDayOfMonth = mCurrentDate.get(Calendar.DAY_OF_MONTH); + if (prevDayOfMonth == dayOfMonth) { + return 0; + } + int maxDayOfMonth = mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH); + if (dayOfMonth == 1 && prevDayOfMonth == maxDayOfMonth) { + return 1; + } + if (dayOfMonth == maxDayOfMonth && prevDayOfMonth == 1) { + return -1; + } + return dayOfMonth - prevDayOfMonth; + } + + /** + * @return The delta months from the current date and the given + * <code>month</code>. + */ + private int getDelataMonth(int month) { + int prevMonth = mCurrentDate.get(Calendar.MONTH); + if (prevMonth == month) { + return 0; + } + if (month == 0 && prevMonth == 11) { + return 1; + } + if (month == 11 && prevMonth == 0) { + return -1; + } + return month - prevMonth; } /** - * Updates the mini-month with the given year, month, and day selected by the - * number pickers. + * Updates the calendar view with the given year, month, and day selected by + * the number spinners. */ - private void updateMiniMonth() { - Calendar selectedDay = mMiniMonthDayPicker.getSelectedDay(); - if (selectedDay.get(Calendar.YEAR) != mYearPicker.getCurrent() - || selectedDay.get(Calendar.MONTH) != mMonthPicker.getCurrent() - || selectedDay.get(Calendar.DAY_OF_MONTH) != mDayPicker.getCurrent()) { - mMiniMonthDayPicker.goTo(mYearPicker.getCurrent(), mMonthPicker.getCurrent(), - mDayPicker.getCurrent(), false, true, false); + private void updateCalendarView() { + mTempDate.setTimeInMillis(mCalendarView.getDate()); + if (mTempDate.get(Calendar.YEAR) != mYearSpinner.getValue() + || mTempDate.get(Calendar.MONTH) != mMonthSpinner.getValue() + || mTempDate.get(Calendar.DAY_OF_MONTH) != mDaySpinner.getValue()) { + mTempDate.clear(); + mTempDate.set(mYearSpinner.getValue(), mMonthSpinner.getValue(), + mDaySpinner.getValue()); + mCalendarView.setDate(mTempDate.getTimeInMillis(), false, false); } } @@ -372,21 +588,21 @@ public class DatePicker extends FrameLayout { * @return The selected year. */ public int getYear() { - return mYearPicker.getCurrent(); + return mYearSpinner.getValue(); } /** * @return The selected month. */ public int getMonth() { - return mMonthPicker.getCurrent(); + return mMonthSpinner.getValue(); } /** * @return The selected day of month. */ public int getDayOfMonth() { - return mDayPicker.getCurrent(); + return mDaySpinner.getValue(); } /** @@ -394,8 +610,8 @@ public class DatePicker extends FrameLayout { */ private void notifyDateChanged() { if (mOnDateChangedListener != null) { - mOnDateChangedListener.onDateChanged(DatePicker.this, mYearPicker.getCurrent(), - mMonthPicker.getCurrent(), mDayPicker.getCurrent()); + mOnDateChangedListener.onDateChanged(DatePicker.this, mYearSpinner.getValue(), + mMonthSpinner.getValue(), mDaySpinner.getValue()); } } diff --git a/core/java/android/widget/DayPicker.java b/core/java/android/widget/DayPicker.java deleted file mode 100644 index 02805be..0000000 --- a/core/java/android/widget/DayPicker.java +++ /dev/null @@ -1,1532 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.widget; - -import com.android.internal.R; - -import android.app.Service; -import android.content.Context; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.Paint.Style; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.format.Time; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView.OnScrollListener; - -import java.security.InvalidParameterException; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Locale; - -import libcore.icu.LocaleData; - -/** - * Displays a day picker in the form of a calendar. The calendar - * is represented as a list where each row depicts a week. Each week is - * composed of items that are selectable days. - */ -public class DayPicker extends FrameLayout { - - /** - * The number of milliseconds in a day. - * - * @hide - */ - protected static final long MILLIS_IN_DAY = 86400000L; - - /** - * The number of day in a week. - * - * @hide - */ - protected static final int DAYS_PER_WEEK = 7; - - /** - * The number of milliseconds in a week. - * - * @hide - */ - protected static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY; - - /** - * Affects when the month selection will change while scrolling up - * - * @hide - */ - protected static final int SCROLL_HYST_WEEKS = 2; - - /** - * How long the GoTo fling animation should last. - * - * @hide - */ - protected static final int GOTO_SCROLL_DURATION = 1000; - - /** - * The duration of the adjustment upon a user scroll in milliseconds. - * - * @hide - */ - protected static final int ADJUSTMENT_SCROLL_DURATION = 500; - - /** - * How long to wait after receiving an onScrollStateChanged notification - * before acting on it. - * - * @hide - */ - protected static final int SCROLL_CHANGE_DELAY = 40; - - /** - * The scale used to compensate for different screen density. - * - * @hide - */ - protected static float sScale; - - /** - * The top offset of the weeks list. - * - * @hide - */ - protected static int mListTopOffset = 2; - - /** - * The visible height of a week view. - * - * @hide - */ - protected int mWeekMinVisibleHeight = 12; - - - /** - * The visible height of a week view. - * - * @hide - */ - protected int mBottomBuffer = 20; - - /** - * The number of shown weeks. - * - * @hide - */ - protected int mShownWeekCount = 6; - - /** - * Flag whether to show the week number. - * - * @hide - */ - protected boolean mShowWeekNumber = true; - - /** - * The number of day per week to be shown - * - * @hide - */ - protected int mDaysPerWeek = 7; - - /** - * The friction of the week list while flinging. - * - * @hide - */ - protected float mFriction = .05f; - - /** - * Scale for adjusting velocity of the week list while flinging. - * - * @hide - */ - protected float mVelocityScale = 0.333f; - - /** - * The adapter for the weeks list. - * - * @hide - */ - protected WeeksAdapter mAdapter; - - /** - * The weeks list. - * - * @hide - */ - protected ListView mListView; - - /** - * The name of the month to display. - * - * @hide - */ - protected TextView mMonthName; - - /** - * The header with week day names. - * - * @hide - */ - protected ViewGroup mDayNamesHeader; - - /** - * Cached labels for the week names header. - * - * @hide - */ - protected String[] mDayLabels; - - /** - * Temporary instance to avoid multiple instantiations. - * - * @hide - */ - protected Calendar mTempCalendar = Calendar.getInstance(); - - /** - * The first day of the week based on the current locale. - * - * @hide - */ - protected int mFirstDayOfWeek = LocaleData.get(Locale.getDefault()).firstDayOfWeek; - - /** - * The first day of the focused month. - * - * @hide - */ - protected Calendar mFirstDayOfMonth = Calendar.getInstance(); - - /** - * Which month should be displayed/highlighted [0-11] - * - * @hide - */ - protected int mCurrentMonthDisplayed; - - /** - * Used for tracking during a scroll. - * - * @hide - */ - protected long mPreviousScrollPosition; - - /** - * Used for tracking which direction the view is scrolling. - * - * @hide - */ - protected boolean mIsScrollingUp = false; - - /** - * The previous scroll state of the weeks ListView. - * - * @hide - */ - protected int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * The current scroll state of the weeks ListView. - * - * @hide - */ - protected int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE; - - /** - * Listener for changes in the selected day. - * - * @hide - */ - protected OnSelectedDayChangeListener mOnChangeListener; - - /** - * Command for adjusting the position after a scroll/fling. - * - * @hide - */ - protected ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable(); - - /** - * The start date of the range supported by this picker. - * - * @hide - */ - protected Calendar mRangeStartDate = Calendar.getInstance(); - - /** - * The end date of the range supported by this picker. - * - * @hide - */ - protected Calendar mRangeEndDate = Calendar.getInstance(); - - /** - * String for formatting the month name in the title text view. - * - * @hide - */ - protected String mMonthNameFormatSrting = "MMMM, yyyy"; - - /** - * The callback used to indicate the user changes the date. - */ - public interface OnSelectedDayChangeListener { - - /** - * Called upon change of the selected day. - * - * @param view The view associated with this listener. - * @param year The year that was set. - * @param month The month that was set [0-11]. - * @param dayOfMonth The day of the month that was set. - */ - public void onSelectedDayChange(DayPicker view, int year, int month, int dayOfMonth); - } - - public DayPicker(Context context) { - this(context, null); - } - - public DayPicker(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DayPicker(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, 0); - - LayoutInflater layoutInflater = (LayoutInflater) mContext - .getSystemService(Service.LAYOUT_INFLATER_SERVICE); - View content = layoutInflater.inflate(R.layout.day_picker, null, false); - addView(content); - - mListView = (ListView) findViewById(R.id.list); - mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names); - mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name); - - // Adjust sizes for screen density - if (sScale == 0) { - sScale = mContext.getResources().getDisplayMetrics().density; - if (sScale != 1) { - mWeekMinVisibleHeight *= sScale; - mBottomBuffer *= sScale; - mListTopOffset *= sScale; - } - } - - // set default range - mRangeStartDate.clear(); - mRangeStartDate.set(1900, 0, 1); - mRangeEndDate.clear(); - mRangeEndDate.set(2100, 0, 1); - - setUpHeader(); - updateHeader(); - setUpListView(); - setUpAdapter(); - - // go to today now - mTempCalendar.setTimeInMillis(System.currentTimeMillis()); - goTo(mTempCalendar, false, true, true); - invalidate(); - } - - /** - * Sets the range supported by this day picker. This is the picker will not - * support dates before <code>startRangeDate</code> and <code>endRangeDate - * </code>. - * - * @param startRangeDate The start date. - * @param endRangeDate The end date. - */ - public void setRange(Calendar startRangeDate, Calendar endRangeDate) { - boolean rangeChanged = false; - if (mRangeStartDate.get(Calendar.DAY_OF_YEAR) != startRangeDate.get(Calendar.DAY_OF_YEAR) - || mRangeStartDate.get(Calendar.YEAR) != startRangeDate.get(Calendar.YEAR)) { - mRangeStartDate.setTimeInMillis(startRangeDate.getTimeInMillis()); - mRangeStartDate.setTimeZone(startRangeDate.getTimeZone()); - rangeChanged = true; - } - if (mRangeEndDate.get(Calendar.DAY_OF_YEAR) != endRangeDate.get(Calendar.DAY_OF_YEAR) - || mRangeEndDate.get(Calendar.YEAR) != endRangeDate.get(Calendar.YEAR)) { - mRangeEndDate.setTimeInMillis(endRangeDate.getTimeInMillis()); - mRangeEndDate.setTimeZone(endRangeDate.getTimeZone()); - rangeChanged = true; - } - - if (!rangeChanged) { - return; - } - - // now recreate the adapter since we have a new range to handle - mAdapter = null; - setUpAdapter(); - - // set the current date to today if in the range - // otherwise to the closest end of the range - mTempCalendar.clear(); - mTempCalendar.setTimeInMillis(System.currentTimeMillis()); - if (mTempCalendar.before(mRangeStartDate)) { - goTo(mRangeStartDate, false, true, true); - } else if (mTempCalendar.after(mRangeEndDate)) { - goTo(mRangeEndDate, false, true, true); - } else { - goTo(mTempCalendar, false, true, true); - } - } - - /** - * Sets the listener to be notified upon day selection changes. - * - * @param listener The listener to be called back. - */ - public void setOnDateChangeListener(OnSelectedDayChangeListener listener) { - mOnChangeListener = listener; - } - - /** - * Gets the selected day. - * - * @return The selected day. - */ - public Calendar getSelectedDay() { - return (Calendar) mAdapter.mSelectedDay.clone(); - } - - /** - * Sets the selected day. This is equivalent to a call to - * {@link #goTo(Calendar, boolean, boolean, boolean)} with - * the arguments <code>selectedDay</code>, <code>false</code>, - * <code>true</code>, <code>false</code> respectively. - * - * @param selectedDay The selected day. - */ - public void setSelectedDay(Calendar selectedDay) { - goTo(selectedDay, false, true, false); - } - - /** - * Creates a new adapter if necessary and sets up its parameters. Override - * this method to provide a custom adapter. - * - * @hide - */ - protected void setUpAdapter() { - if (mAdapter == null) { - mAdapter = new WeeksAdapter(getContext()); - mAdapter.registerDataSetObserver(new DataSetObserver() { - @Override - public void onChanged() { - if (mOnChangeListener != null) { - Calendar selectedDay = mAdapter.getSelectedDay(); - mOnChangeListener.onSelectedDayChange(DayPicker.this, - selectedDay.get(Calendar.YEAR), - selectedDay.get(Calendar.MONTH), - selectedDay.get(Calendar.DAY_OF_MONTH)); - } - } - }); - mListView.setAdapter(mAdapter); - } - - // refresh the view with the new parameters - mAdapter.notifyDataSetChanged(); - } - - /** - * Sets up the strings to be used by the header. Override this method to use - * different strings or modify the view params. - * - * @hide - */ - protected void setUpHeader() { - mDayLabels = new String[mDaysPerWeek]; - for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) { - int calendarDay = (i < mDaysPerWeek) ? i : 1; // Calendar.MONDAY is - // 1 - mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay, - DateUtils.LENGTH_SHORTEST); - } - } - - /** - * Sets all the required fields for the list view. Override this method to - * set a different list view behavior. - * - * @hide - */ - protected void setUpListView() { - // Configure the listview - mListView.setDivider(null); - mListView.setItemsCanFocus(true); - mListView.setVerticalScrollBarEnabled(false); - mListView.setOnScrollListener(new OnScrollListener() { - public void onScrollStateChanged(AbsListView view, int scrollState) { - DayPicker.this.onScrollStateChanged(view, scrollState); - } - - public void onScroll( - AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - DayPicker.this.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); - } - }); - // Make the scrolling behavior nicer - mListView.setFriction(mFriction); - mListView.setVelocityScale(mVelocityScale); - } - - /** - * Fixes the day names header to provide correct spacing and updates the - * label text. Override this to set up a custom header. - * - * @hide - */ - protected void updateHeader() { - TextView label = (TextView) mDayNamesHeader.getChildAt(0); - if (mShowWeekNumber) { - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); - } - for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) { - label = (TextView) mDayNamesHeader.getChildAt(i); - if (i < mDaysPerWeek + 1) { - label.setText(mDayLabels[i - 1]); - label.setVisibility(View.VISIBLE); - } else { - label.setVisibility(View.GONE); - } - } - mDayNamesHeader.invalidate(); - } - - /** - * This moves to the specified time in the view. If the time is not already - * in range it will move the list so that the first of the month containing - * the time is at the top of the view. If the new time is already in view - * the list will not be scrolled unless forceScroll is true. This time may - * optionally be highlighted as selected as well. - * - * @param year The year to move to. - * @param month The month to move to <strong>starting from zero<strong>. - * @param dayOfMonth The month day to move to. - * @param animate Whether to scroll to the given time or just redraw at the - * new location. - * @param setSelected Whether to set the given time as selected - * @param forceScroll Whether to recenter even if the time is already - * visible. - * - * @throws IllegalArgumentException of the provided date is before the - * range start of after the range end. - * - * @see #setRange(Calendar, Calendar) - */ - public void goTo(int year, int month, int dayOfMonth, boolean animate, boolean setSelected, - boolean forceScroll) { - mTempCalendar.clear(); - mTempCalendar.set(year, month, dayOfMonth); - goTo(mTempCalendar, animate, setSelected, forceScroll); - } - - /** - * This moves to the specified time in the view. If the time is not already - * in range it will move the list so that the first of the month containing - * the time is at the top of the view. If the new time is already in view - * the list will not be scrolled unless forceScroll is true. This time may - * optionally be highlighted as selected as well. - * - * @param date The time to move to. - * @param animate Whether to scroll to the given time or just redraw at the - * new location. - * @param setSelected Whether to set the given time as selected. - * @param forceScroll Whether to recenter even if the time is already - * visible. - * - * @throws IllegalArgumentException of the provided date is before the - * range start of after the range end. - * - * @see #setRange(Calendar, Calendar) - */ - public void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) { - if (date.before(mRangeStartDate) || date.after(mRangeEndDate)) { - throw new IllegalArgumentException("Time not between " + mRangeStartDate.getTime() - + " and " + mRangeEndDate.getTime()); - } - // Find the first and last entirely visible weeks - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - View firstChild = mListView.getChildAt(0); - if (firstChild != null && firstChild.getTop() < 0) { - firstFullyVisiblePosition++; - } - int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1; - if (firstChild != null && firstChild.getTop() > mBottomBuffer) { - lastFullyVisiblePosition--; - } - if (setSelected) { - mAdapter.setSelectedDay(date); - } - // Get the week we're going to - int position = getWeeksDelta(date); - - // Check if the selected day is now outside of our visible range - // and if so scroll to the month that contains it - if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition - || forceScroll) { - mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis()); - mFirstDayOfMonth.setTimeZone(date.getTimeZone()); - mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1); - - setMonthDisplayed(mFirstDayOfMonth); - position = getWeeksDelta(mFirstDayOfMonth); - - mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING; - if (animate) { - mListView.smoothScrollToPositionFromTop(position, mListTopOffset, - GOTO_SCROLL_DURATION); - } else { - mListView.setSelectionFromTop(position, mListTopOffset); - // Perform any after scroll operations that are needed - onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE); - } - } else if (setSelected) { - // Otherwise just set the selection - setMonthDisplayed(date); - } - } - - /** - * Called when a <code>view</code> transitions to a new <code>scrollState - * </code>. - * - * @hide - */ - protected void onScrollStateChanged(AbsListView view, int scrollState) { - mScrollStateChangedRunnable.doScrollStateChange(view, scrollState); - } - - /** - * Updates the title and selected month if the <code>view</code> has moved to a new - * month. - * - * @hide - */ - protected void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, - int totalItemCount) { - WeekView child = (WeekView) view.getChildAt(0); - if (child == null) { - return; - } - - // Figure out where we are - long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom(); - - // If we have moved since our last call update the direction - if (currScroll < mPreviousScrollPosition) { - mIsScrollingUp = true; - } else if (currScroll > mPreviousScrollPosition) { - mIsScrollingUp = false; - } else { - return; - } - - // Use some hysteresis for checking which month to highlight. This - // causes the month to transition when two full weeks of a month are - // visible when scrolling up, and when the first day in a month reaches - // the top of the screen when scrolling down. - int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0; - if (mIsScrollingUp) { - child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset); - } else if (offset != 0) { - child = (WeekView) view.getChildAt(offset); - } - - // Find out which month we're moving into - int month; - if (mIsScrollingUp) { - month = child.getMonthOfFirstWeekDay(); - } else { - month = child.getMonthOfLastWeekDay(); - } - - // And how it relates to our current highlighted month - int monthDiff; - if (mCurrentMonthDisplayed == 11 && month == 0) { - monthDiff = 1; - } else if (mCurrentMonthDisplayed == 0 && month == 11) { - monthDiff = -1; - } else { - monthDiff = month - mCurrentMonthDisplayed; - } - - // Only switch months if we're scrolling away from the currently - // selected month - if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) { - Calendar firstDay = child.getFirstDay(); - if (mIsScrollingUp) { - firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK); - } else { - firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK); - } - setMonthDisplayed(firstDay); - } - mPreviousScrollPosition = currScroll; - mPreviousScrollState = mCurrentScrollState; - } - - /** - * Sets the month displayed at the top of this view based on time. Override - * to add custom events when the title is changed. - * - * @param calendar A day in the new focus month. - * - * @hide - */ - protected void setMonthDisplayed(Calendar calendar) { - mMonthName.setText(DateFormat.format(mMonthNameFormatSrting, calendar)); - mMonthName.invalidate(); - mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); - mAdapter.setFocusMonth(mCurrentMonthDisplayed); - // TODO Send Accessibility Event - } - - /** - * @return Returns the number of weeks between the current week day of the - * <code>fromDate</code> and the first day of week of - * <code>toDate</code>. - * - * @hide - */ - protected int getWeeksDelta(Calendar toDate) { - if (toDate.before(mRangeStartDate)) { - throw new IllegalArgumentException("fromDate: " + mRangeStartDate.getTime() - + " does not precede toDate: " + toDate.getTime()); - } - - int fromDateDayOfWeek = mRangeStartDate.get(Calendar.DAY_OF_WEEK); - long diff = (fromDateDayOfWeek - mFirstDayOfWeek) * MILLIS_IN_DAY; - if (diff < 0) { - diff = diff + MILLIS_IN_WEEK; - } - long refDay = mRangeStartDate.getTimeInMillis() - diff; - return (int) ((toDate.getTimeInMillis() - refDay) / MILLIS_IN_WEEK); - } - - /** - * Command responsible for acting upon scroll state changes. - * - * @hide - */ - protected class ScrollStateRunnable implements Runnable { - private AbsListView mView; - - private int mNewState; - - /** - * Sets up the runnable with a short delay in case the scroll state - * immediately changes again. - * - * @param view The list view that changed state - * @param scrollState The new state it changed to - */ - public void doScrollStateChange(AbsListView view, int scrollState) { - removeCallbacks(this); - mView = view; - mNewState = scrollState; - removeCallbacks(this); - postDelayed(this, SCROLL_CHANGE_DELAY); - } - - public void run() { - mCurrentScrollState = mNewState; - // Fix the position after a scroll or a fling ends - if (mNewState == OnScrollListener.SCROLL_STATE_IDLE - && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) { - mPreviousScrollState = mNewState; - View child = mView.getChildAt(0); - if (child == null) { - // The view is no longer visible, just return - return; - } - int dist = child.getBottom() - mListTopOffset; - if (dist > mListTopOffset) { - if (mIsScrollingUp) { - mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION); - } else { - mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION); - } - } - } else { - mPreviousScrollState = mNewState; - } - } - } - - /** - * <p> - * This is a specialized adapter for creating a list of weeks with - * selectable days. It can be configured to display the week number, start - * the week on a given day, show a reduced number of days, or display an - * arbitrary number of weeks at a time. - * </p> - * - * @hide - */ - public class WeeksAdapter extends BaseAdapter implements OnTouchListener { - - /** - * The default maximum year supported by the Date Time Picker. - */ - public static final int DEFAULT_MAX_CALENDAR_YEAR = 2100; - - /** - * The default minimum year supported by the Date Time Picker. - */ - public static final int DEFAULT_MIN_CALENDAR_YEAR = 1900; - - /** - * The number of weeks to display at a time. - */ - public static final String WEEK_PARAMS_NUM_WEEKS = "num_weeks"; - - /** - * Which month should be in focus currently. - */ - public static final String WEEK_PARAMS_FOCUS_MONTH = "focus_month"; - - /** - * Whether the week number should be shown. Non-zero to show them. - */ - public static final String WEEK_PARAMS_SHOW_WEEK = "week_numbers"; - - /** - * Which day the week should start on. {@link Time#SUNDAY} through - * {@link Time#SATURDAY}. - */ - public static final String WEEK_PARAMS_WEEK_START = "week_start"; - - /** - * The year of the highlighted day. - */ - public static final String WEEK_PARAMS_YEAR = "selected_year"; - - /** - * The month of the highlighted day. - */ - public static final String WEEK_PARAMS_MONTH = "selected_month"; - - /** - * The year of the highlighted day. - */ - public static final String WEEK_PARAMS_DAY_OF_MONTH = "selected_day_of_month"; - - /** - * The start date of the supported interval. - */ - public static final String WEEK_PARAMS_START_DATE_RANGE_MILLIS = "start_date_gange_millis"; - - /** - * The end date of the supported interval. - */ - public static final String WEEK_PARAMS_END_DATE_RANGE_MILLIS = "end_date_gange_millis"; - - /** - * How many days of the week to display [1-7]. - */ - public static final String WEEK_PARAMS_DAYS_PER_WEEK = "days_per_week"; - - protected int WEEK_7_OVERHANG_HEIGHT = 7; - - protected int mSelectedWeek; - - protected GestureDetector mGestureDetector; - - protected int mFocusMonth = 0; - - private final Calendar mSelectedDay = Calendar.getInstance(); - - private int mTotalWeekCount = -1; - - public WeeksAdapter(Context context) { - mContext = context; - - if (sScale == 0) { - sScale = context.getResources().getDisplayMetrics().density; - if (sScale != 1) { - WEEK_7_OVERHANG_HEIGHT *= sScale; - } - } - init(); - } - - /** - * Set up the gesture detector and selected time - */ - protected void init() { - mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener()); - mSelectedWeek = getWeeksDelta(mSelectedDay); - - // make adjustment to fit the range last week with needed overflow - mTempCalendar.setTimeInMillis(mRangeEndDate.getTimeInMillis()); - mTempCalendar.setTimeZone(mRangeEndDate.getTimeZone()); - int diff = mFirstDayOfWeek - mRangeEndDate.get(Calendar.DAY_OF_WEEK); - if (diff < 0) { - diff += DAYS_PER_WEEK; - } - mTempCalendar.add(Calendar.DAY_OF_WEEK, diff); - mTotalWeekCount = getWeeksDelta(mTempCalendar); - } - - /** - * Updates the selected day and related parameters. - * - * @param selectedDay The time to highlight - */ - public void setSelectedDay(Calendar selectedDay) { - if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDay.get(Calendar.DAY_OF_YEAR) - && selectedDay.get(Calendar.YEAR) == mSelectedDay.get(Calendar.YEAR)) { - return; - } - mSelectedDay.setTimeInMillis(selectedDay.getTimeInMillis()); - mSelectedDay.setTimeZone(selectedDay.getTimeZone()); - mSelectedWeek = getWeeksDelta(mSelectedDay); - mFocusMonth = mSelectedDay.get(Calendar.MONTH); - notifyDataSetChanged(); - } - - /** - * @return The selected day of month. - */ - public Calendar getSelectedDay() { - return mSelectedDay; - } - - /** - * updates any config options that may have changed and refreshes the - * view - */ - public void refresh() { - notifyDataSetChanged(); - } - - @Override - public int getCount() { - return mTotalWeekCount; - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return position; - } - - @SuppressWarnings("unchecked") - @Override - public View getView(int position, View convertView, ViewGroup parent) { - WeekView v; - HashMap<String, Object> drawingParams = null; - if (convertView != null) { - v = (WeekView) convertView; - // We store the drawing parameters in the view so it can be - // recycled - drawingParams = (HashMap<String, Object>) v.getTag(); - } else { - v = getNewView(); - // Set up the new view - android.widget.AbsListView.LayoutParams params = - new android.widget.AbsListView.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - v.setLayoutParams(params); - v.setClickable(true); - v.setOnTouchListener(this); - - drawingParams = new HashMap<String, Object>(); - } - - // pass in all the view parameters - putDrawingParementer(drawingParams, WeekView.VIEW_PARAMS_SHOW_WK_NUM, - mShowWeekNumber ? 1 : 0); - putDrawingParementer(drawingParams, WeekView.VIEW_PARAMS_WEEK_START, mFirstDayOfWeek); - putDrawingParementer(drawingParams, WeekView.VIEW_PARAMS_NUM_DAYS, mDaysPerWeek); - putDrawingParementer(drawingParams, WeekView.VIEW_PARAMS_WEEK, position); - putDrawingParementer(drawingParams, WeekView.VIEW_PARAMS_FOCUS_MONTH, mFocusMonth); - putDrawingParementer(drawingParams, WeekView.VIEW_PARAMS_SELECTED_DAY, - (mSelectedWeek == position) ? mSelectedDay.get(Calendar.DAY_OF_WEEK) : -1); - v.setWeekParams(drawingParams); - - return v; - } - - /** - * Puts the given <code>value</code> for the drawing - * <code>parameter</code> in the <code>drawingParams</code>. - */ - private void putDrawingParementer(HashMap<String, Object> drawingParams, String parameter, - int value) { - int[] valueArray = (int[]) drawingParams.get(parameter); - if (valueArray == null) { - valueArray = new int[1]; - drawingParams.put(parameter, valueArray); - } - valueArray[0] = value; - } - - /** - * Creates a new WeekView and returns it. Override this to customize the - * view creation. - * - * @return A new WeekView - */ - protected WeekView getNewView() { - return new WeekView(mContext); - } - - /** - * Changes which month is in focus and updates the view. - * - * @param month The month to show as in focus [0-11] - */ - public void setFocusMonth(int month) { - if (mFocusMonth == month) { - return; - } - mFocusMonth = month; - notifyDataSetChanged(); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (mGestureDetector.onTouchEvent(event)) { - WeekView weekView = (WeekView) v; - weekView.getDayFromLocation(event.getX(), mTempCalendar); - // it is possible that the touched day is outside the valid range - // we draw whole weeks but range end can fall not on the week end - if (mTempCalendar.before(mRangeStartDate) || mTempCalendar.after(mRangeEndDate)) { - return true; - } - onDayTapped(mTempCalendar); - return true; - } - return false; - } - - /** - * Maintains the same hour/min/sec but moves the day to the tapped day. - * - * @param day The day that was tapped - */ - protected void onDayTapped(Calendar day) { - setSelectedDay(day); - setMonthDisplayed(day); - } - - /** - * This is here so we can identify single tap events and set the - * selected day correctly - */ - protected class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onSingleTapUp(MotionEvent e) { - return true; - } - } - } - - /** - * <p> - * This is a dynamic view for drawing a single week. It can be configured to - * display the week number, start the week on a given day, or show a reduced - * number of days. It is intended for use as a single view within a - * ListView. See {@link WeeksAdapter} for usage. - * </p> - * - * @hide - */ - public class WeekView extends View { - - /* - * These params can be passed into the view to control how it appears. - * {@link #VIEW_PARAMS_WEEK} is the only required field, though the - * default values are unlikely to fit most layouts correctly. - */ - - /** - * This sets the height of this week in pixels - */ - public static final String VIEW_PARAMS_HEIGHT = "height"; - - /** - * This specifies the position (or weeks since the epoch) of this week. - */ - public static final String VIEW_PARAMS_WEEK = "week"; - - /** - * This sets one of the days in this view as selected - * {@link Time#SUNDAY} through {@link Time#SATURDAY}. - */ - public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day"; - - /** - * Which day the week should start on. {@link Time#SUNDAY} through - * {@link Time#SATURDAY}. - */ - public static final String VIEW_PARAMS_WEEK_START = "week_start"; - - /** - * How many days to display at a time. Days will be displayed starting - * with {@link #mFirstDay}. - */ - public static final String VIEW_PARAMS_NUM_DAYS = "num_days"; - - /** - * Which month is currently in focus, as defined by {@link Time#month} - * [0-11]. - */ - public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month"; - - /** - * If this month should display week numbers. false if 0, true - * otherwise. - */ - public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num"; - - protected static final int DEFAULT_SHOW_WK_NUM = 0; - - protected final int mWeekSeperatorWidth; - - protected final int mNumberTextSize; - - protected final int mWeekDayPadding; - - protected final Rect mTempRect = new Rect(); - - protected final Paint mDrawPaint = new Paint(); - - protected final Paint mMonthNumDrawPaint = new Paint(); - - protected final Drawable mSelectedDayLine; - - protected final int mSelectedDayLineWidth; - - protected final int mSelectionBackgroundColor; - - protected final int mFocusedMonthDateColor; - - protected final int mOtherMonthDateColor; - - protected final int mGridLinesColor; - - protected final int mWeekNumberColor; - - protected final int mFirstDayOfweek; - - // Cache the number strings so we don't have to recompute them each time - protected String[] mDayNumbers; - - // Quick lookup for checking which days are in the focus month - protected boolean[] mFocusDay; - - // The first day displayed by this item - protected Calendar mFirstDay; - - // The month of the first day in this week - protected int mMonthOfFirstWeekDay = -1; - - // The month of the last day in this week - protected int mLastWeekDayMonth = -1; - - // The position of this week, equivalent to weeks since the week of Jan - // 1st, 1900 - protected int mWeek = -1; - - // Quick reference to the width of this view, matches parent - protected int mWidth; - - // The height this view should draw at in pixels, set by height param - protected int mHeight; - - // If this view contains the selected day - protected boolean mHasSelectedDay = false; - - // Which day is selected [0-6] or -1 if no day is selected - protected int mSelectedDay = -1; - - // How many days to display - protected int mWeekDayCount; - - // The number of days + a spot for week number if it is displayed - protected int mNumCells; - - // The left edge of the selected day - protected int mSelectedLeft = -1; - - // The right edge of the selected day - protected int mSelectedRight = -1; - - public WeekView(Context context) { - super(context); - - TypedValue outTypedValue = new TypedValue(); - context.getTheme().resolveAttribute(R.attr.dayPickerWeekViewStyle, outTypedValue, true); - TypedArray attributesArray = context.obtainStyledAttributes(outTypedValue.resourceId, - R.styleable.DayPickerWeekView); - mHeight = attributesArray.getDimensionPixelSize(R.styleable.DayPickerWeekView_height, - 26); - mNumberTextSize = attributesArray.getDimensionPixelSize( - R.styleable.DayPickerWeekView_textSize, 14); - mFirstDayOfweek = attributesArray.getInt(R.styleable.DayPickerWeekView_weekStartDay, - Calendar.SUNDAY); - mNumCells = mWeekDayCount = attributesArray.getInt( - R.styleable.DayPickerWeekView_weekDayCount, 7); - mShowWeekNumber = attributesArray.getBoolean(R.styleable.DayPickerWeekView_weekDayCount, - true); - mWeekSeperatorWidth = attributesArray.getDimensionPixelSize( - R.styleable.DayPickerWeekView_weekSeperatorWidth, 1); - mWeekDayPadding = attributesArray.getDimensionPixelSize( - R.styleable.DayPickerWeekView_weekDayPadding, 0); - mSelectionBackgroundColor = attributesArray.getColor( - R.styleable.DayPickerWeekView_selectionBackgroundColor, 0); - mFocusedMonthDateColor = attributesArray.getColor( - R.styleable.DayPickerWeekView_focusedMonthDateColor, 0); - mOtherMonthDateColor = attributesArray.getColor( - R.styleable.DayPickerWeekView_otherMonthDateColor, 0); - mGridLinesColor = attributesArray.getColor( - R.styleable.DayPickerWeekView_gridLinesColor, 0); - mWeekNumberColor = attributesArray.getColor( - R.styleable.DayPickerWeekView_weekNumberColor, 0); - mSelectedDayLine = attributesArray - .getDrawable(R.styleable.DayPickerWeekView_selectedDayLine); - mSelectedDayLineWidth = attributesArray.getDimensionPixelSize( - R.styleable.DayPickerWeekView_selectedDayLineWidth, 6); - attributesArray.recycle(); - - // Sets up any standard paints that will be used - setPaintProperties(); - } - - /** - * Sets all the parameters for displaying this week. The only required - * parameter is the week number. Other parameters have a default value - * and will only update if a new value is included, except for focus - * month, which will always default to no focus month if no value is - * passed in. See {@link #VIEW_PARAMS_HEIGHT} for more info on - * parameters. - * - * @param params A map of the new parameters, see - * {@link #VIEW_PARAMS_HEIGHT} - */ - public void setWeekParams(HashMap<String, Object> params) { - if (!params.containsKey(VIEW_PARAMS_WEEK)) { - throw new InvalidParameterException( - "You must specify the week number for this view"); - } - setTag(params); - // We keep the current value for any params not present - if (params.containsKey(VIEW_PARAMS_HEIGHT)) { - mHeight = ((int[]) params.get(VIEW_PARAMS_HEIGHT))[0]; - } - if (params.containsKey(VIEW_PARAMS_SELECTED_DAY)) { - mSelectedDay = ((int[]) params.get(VIEW_PARAMS_SELECTED_DAY))[0]; - } - mHasSelectedDay = mSelectedDay != -1; - if (params.containsKey(VIEW_PARAMS_NUM_DAYS)) { - mWeekDayCount = ((int[]) params.get(VIEW_PARAMS_NUM_DAYS))[0]; - } - if (params.containsKey(VIEW_PARAMS_SHOW_WK_NUM)) { - if (((int[]) params.get(VIEW_PARAMS_SHOW_WK_NUM))[0] != 0) { - mNumCells = mWeekDayCount + 1; - mShowWeekNumber = true; - } else { - mShowWeekNumber = false; - } - } else { - mNumCells = mShowWeekNumber ? mWeekDayCount + 1 : mWeekDayCount; - } - mWeek = ((int[]) params.get(VIEW_PARAMS_WEEK))[0]; - mTempCalendar.setTimeInMillis(mRangeStartDate.getTimeInMillis()); - mTempCalendar.setTimeZone(mRangeStartDate.getTimeZone()); - mTempCalendar.add(Calendar.WEEK_OF_YEAR, mWeek); - if (params.containsKey(VIEW_PARAMS_WEEK_START)) { - mTempCalendar.setFirstDayOfWeek(((int[]) params.get(VIEW_PARAMS_WEEK_START))[0]); - } else { - mTempCalendar.setFirstDayOfWeek(Calendar.SUNDAY); - } - - // Allocate space for caching the day numbers and focus values - mDayNumbers = new String[mNumCells]; - mFocusDay = new boolean[mNumCells]; - - // If we're showing the week number calculate it based on Monday - int i = 0; - if (mShowWeekNumber) { - mDayNumbers[0] = Integer.toString(mTempCalendar.get(Calendar.WEEK_OF_YEAR)); - i++; - } - - // Now adjust our starting day based on the start day of the week - int diff = mTempCalendar.getFirstDayOfWeek() - mTempCalendar.get(Calendar.DAY_OF_WEEK); - mTempCalendar.add(Calendar.DAY_OF_MONTH, diff); - - mFirstDay = (Calendar) mTempCalendar.clone(); - - mMonthOfFirstWeekDay = mTempCalendar.get(Calendar.MONTH); - - int focusMonth = params.containsKey(VIEW_PARAMS_FOCUS_MONTH) ? ((int[]) params - .get(VIEW_PARAMS_FOCUS_MONTH))[0] : -1; - - for (; i < mNumCells; i++) { - mFocusDay[i] = (mTempCalendar.get(Calendar.MONTH) == focusMonth); - // do not draw dates outside the valid range to avoid user confusion - if (mTempCalendar.before(mRangeStartDate) || mTempCalendar.after(mRangeEndDate)) { - mDayNumbers[i] = ""; - } else { - mDayNumbers[i] = Integer.toString(mTempCalendar.get(Calendar.DAY_OF_MONTH)); - } - mTempCalendar.add(Calendar.DAY_OF_MONTH, 1); - } - // We do one extra add at the end of the loop, if that pushed us to - // new month undo it - if (mTempCalendar.get(Calendar.DAY_OF_MONTH) == 1) { - mTempCalendar.add(Calendar.DAY_OF_MONTH, -1); - } - mLastWeekDayMonth = mTempCalendar.get(Calendar.MONTH); - - updateSelectionPositions(); - } - - /** - * Sets up the text and style properties for painting. Override this if - * you want to use a different paint. - */ - protected void setPaintProperties() { - mDrawPaint.setFakeBoldText(false); - mDrawPaint.setAntiAlias(true); - mDrawPaint.setTextSize(mNumberTextSize); - mDrawPaint.setStyle(Style.FILL); - - mMonthNumDrawPaint.setFakeBoldText(true); - mMonthNumDrawPaint.setAntiAlias(true); - mMonthNumDrawPaint.setTextSize(mNumberTextSize); - mMonthNumDrawPaint.setColor(mFocusedMonthDateColor); - mMonthNumDrawPaint.setStyle(Style.FILL); - mMonthNumDrawPaint.setTextAlign(Align.CENTER); - } - - /** - * Returns the month of the first day in this week. - * - * @return The month the first day of this view is in. - */ - public int getMonthOfFirstWeekDay() { - return mMonthOfFirstWeekDay; - } - - /** - * Returns the month of the last day in this week - * - * @return The month the last day of this view is in - */ - public int getMonthOfLastWeekDay() { - return mLastWeekDayMonth; - } - - /** - * Returns the first day in this view. - * - * @return The first day in the view. - */ - public Calendar getFirstDay() { - return mFirstDay; - } - - /** - * Returns the number of days this view will display. - */ - public int getNumDays() { - return mWeekDayCount; - } - - /** - * Calculates the day that the given x position is in, accounting for - * week number. Returns a Time referencing that day or null if - * - * @param x The x position of the touch eventy - */ - public void getDayFromLocation(float x, Calendar outCalendar) { - int dayStart = mShowWeekNumber ? (mWidth - mWeekDayPadding * 2) / mNumCells - + mWeekDayPadding : mWeekDayPadding; - if (x < dayStart || x > mWidth - mWeekDayPadding) { - outCalendar.set(0, 0, 0, 0, 0, 0); - return; - } - // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels - int dayPosition = (int) ((x - dayStart) * mWeekDayCount - / (mWidth - dayStart - mWeekDayPadding)); - outCalendar.setTimeZone(mFirstDay.getTimeZone()); - outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis()); - outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition); - } - - @Override - protected void onDraw(Canvas canvas) { - drawBackground(canvas); - drawWeekNums(canvas); - drawDaySeparators(canvas); - drawSelectedDayLines(canvas); - } - - /** - * This draws the selection highlight if a day is selected in this week. - * Override this method if you wish to have a different background - * drawn. - * - * @param canvas The canvas to draw on - */ - protected void drawBackground(Canvas canvas) { - if (!mHasSelectedDay) { - return; - } - mDrawPaint.setColor(mSelectionBackgroundColor); - - mTempRect.top = mWeekSeperatorWidth; - mTempRect.bottom = mHeight; - mTempRect.left = mShowWeekNumber ? mWeekDayPadding + (mWidth - mWeekDayPadding * 2) - / mNumCells : mWeekDayPadding; - mTempRect.right = mSelectedLeft - 2; - canvas.drawRect(mTempRect, mDrawPaint); - - mTempRect.left = mSelectedRight + 3; - mTempRect.right = mWidth - mWeekDayPadding; - canvas.drawRect(mTempRect, mDrawPaint); - } - - /** - * Draws the week and month day numbers for this week. Override this - * method if you need different placement. - * - * @param canvas The canvas to draw on - */ - protected void drawWeekNums(Canvas canvas) { - float textHeight = mDrawPaint.getTextSize(); - int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorWidth; - int nDays = mNumCells; - - mDrawPaint.setTextAlign(Align.CENTER); - int i = 0; - int divisor = 2 * nDays; - if (mShowWeekNumber) { - mDrawPaint.setColor(mWeekNumberColor); - int x = (mWidth - mWeekDayPadding * 2) / divisor + mWeekDayPadding; - canvas.drawText(mDayNumbers[0], x, y, mDrawPaint); - i++; - } - for (; i < nDays; i++) { - mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor - : mOtherMonthDateColor); - int x = (2 * i + 1) * (mWidth - mWeekDayPadding * 2) / divisor + mWeekDayPadding; - canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint); - } - } - - /** - * Draws a horizontal line for separating the weeks. Override this - * method if you want custom separators. - * - * @param canvas The canvas to draw on. - */ - protected void drawDaySeparators(Canvas canvas) { - // If it is the topmost fully visible child do not draw separator line - int firstFullyVisiblePosition = mListView.getFirstVisiblePosition(); - if (mListView.getChildAt(0).getTop() < 0) { - firstFullyVisiblePosition++; - } - if (firstFullyVisiblePosition == mWeek) { - return; - } - mDrawPaint.setColor(mGridLinesColor); - mDrawPaint.setStrokeWidth(mWeekSeperatorWidth); - float x = mShowWeekNumber ? mWeekDayPadding + (mWidth - mWeekDayPadding * 2) / mNumCells - : mWeekDayPadding; - canvas.drawLine(x, 0, mWidth - mWeekDayPadding, 0, mDrawPaint); - } - - /** - * Draws the selected day lines if this week has a selected day. - * - * @param canvas The canvas to draw on - */ - protected void drawSelectedDayLines(Canvas canvas) { - if (!mHasSelectedDay) { - return; - } - mSelectedDayLine.setBounds(mSelectedLeft - mSelectedDayLineWidth / 2, - mWeekSeperatorWidth, - mSelectedLeft + mSelectedDayLineWidth / 2, mHeight); - mSelectedDayLine.draw(canvas); - mSelectedDayLine.setBounds(mSelectedRight - mSelectedDayLineWidth / 2, - mWeekSeperatorWidth, - mSelectedRight + mSelectedDayLineWidth / 2, mHeight); - mSelectedDayLine.draw(canvas); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mWidth = w; - updateSelectionPositions(); - } - - /** - * This calculates the positions for the selected day lines. - */ - protected void updateSelectionPositions() { - if (mHasSelectedDay) { - int selectedPosition = mSelectedDay - mTempCalendar.getFirstDayOfWeek(); - if (selectedPosition < 0) { - selectedPosition += 7; - } - if (mShowWeekNumber) { - selectedPosition++; - } - mSelectedLeft = selectedPosition * (mWidth - mWeekDayPadding * 2) / mNumCells - + mWeekDayPadding; - mSelectedRight = (selectedPosition + 1) * (mWidth - mWeekDayPadding * 2) / mNumCells - + mWeekDayPadding; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight); - } - } -} diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index a236d27..7ad0390 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -30,8 +30,8 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Paint.Align; import android.graphics.Rect; +import android.graphics.Paint.Align; import android.text.InputFilter; import android.text.InputType; import android.text.Spanned; @@ -41,11 +41,11 @@ import android.util.AttributeSet; import android.util.SparseArray; import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.LayoutInflater.Filter; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.LayoutInflater.Filter; import android.view.animation.OvershootInterpolator; import android.view.inputmethod.InputMethodManager; @@ -62,11 +62,18 @@ import android.view.inputmethod.InputMethodManager; * <p> * For an example of using this widget, see {@link android.widget.TimePicker}. * </p> + * + * @attr ref android.R.styleable#NumberPicker_solidColor */ @Widget public class NumberPicker extends LinearLayout { /** + * The default update interval during long press. + */ + private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300; + + /** * The index of the middle selector item. */ private static final int SELECTOR_MIDDLE_ITEM_INDEX = 2; @@ -115,6 +122,8 @@ public class NumberPicker extends LinearLayout { * strings like "01". Keeping a static formatter etc. is the most efficient * way to do this; it avoids creating temporary objects on every call to * format(). + * + * @hide */ public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() { final StringBuilder mBuilder = new StringBuilder(); @@ -123,7 +132,7 @@ public class NumberPicker extends LinearLayout { final Object[] mArgs = new Object[1]; - public String toString(int value) { + public String format(int value) { mArgs[0] = value; mBuilder.delete(0, mBuilder.length()); mFmt.format("%02d", mArgs); @@ -159,22 +168,22 @@ public class NumberPicker extends LinearLayout { /** * Lower value of the range of numbers allowed for the NumberPicker */ - private int mStart; + private int mMinValue; /** * Upper value of the range of numbers allowed for the NumberPicker */ - private int mEnd; + private int mMaxValue; /** * Current value of this NumberPicker */ - private int mCurrent; + private int mValue; /** * Listener to be notified upon current value change. */ - private OnChangeListener mOnChangeListener; + private OnValueChangedListener mOnValueChangedListener; /** * Listener to be notified upon scroll state change. @@ -189,7 +198,7 @@ public class NumberPicker extends LinearLayout { /** * The speed for updating the value form long press. */ - private long mLongPressUpdateInterval = 300; + private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL; /** * Cache for the string representation of selector indices. @@ -308,7 +317,7 @@ public class NumberPicker extends LinearLayout { /** * Flag whether the selector should wrap around. */ - private boolean mWrapSelector; + private boolean mWrapSelectorWheel; /** * The back ground color used to optimize scroller fading. @@ -326,9 +335,10 @@ public class NumberPicker extends LinearLayout { private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; /** - * The callback interface used to indicate the number value has changed. + * Interface to listen for changes of the current value. */ - public interface OnChangeListener { + public interface OnValueChangedListener { + /** * Called upon a change of the current value. * @@ -336,11 +346,11 @@ public class NumberPicker extends LinearLayout { * @param oldVal The previous value. * @param newVal The new value. */ - void onChange(NumberPicker picker, int oldVal, int newVal); + void onValueChange(NumberPicker picker, int oldVal, int newVal); } /** - * Interface for listening to the picker scroll state. + * Interface to listen for the picker scroll state. */ public interface OnScrollListener { @@ -360,27 +370,29 @@ public class NumberPicker extends LinearLayout { public static int SCROLL_STATE_FLING = 2; /** - * Callback method to be invoked while the number picker is being scrolled. + * Callback invoked while the number picker scroll state has changed. * - * @param view The view whose scroll state is being reported - * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE}, - * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}. + * @param view The view whose scroll state is being reported. + * @param scrollState The current scroll state. One of + * {@link #SCROLL_STATE_IDLE}, + * {@link #SCROLL_STATE_TOUCH_SCROLL} or + * {@link #SCROLL_STATE_IDLE}. */ public void onScrollStateChange(NumberPicker view, int scrollState); } /** - * Interface used to format the number into a string for presentation. + * Interface used to format current value into a string for presentation. */ public interface Formatter { /** - * Formats a string representation of the current index. + * Formats a string representation of the current value. * * @param value The currently selected value. * @return A formatted string representation. */ - public String toString(int value); + public String format(int value); } /** @@ -436,9 +448,9 @@ public class NumberPicker extends LinearLayout { public void onClick(View v) { mInputText.clearFocus(); if (v.getId() == R.id.increment) { - changeCurrent(mCurrent + 1); + changeCurrent(mValue + 1); } else { - changeCurrent(mCurrent - 1); + changeCurrent(mValue - 1); } } }; @@ -551,6 +563,9 @@ public class NumberPicker extends LinearLayout { @Override public boolean onInterceptTouchEvent(MotionEvent event) { + if (!isEnabled()) { + return false; + } switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mLastMotionEventY = mLastDownEventY = event.getY(); @@ -594,6 +609,9 @@ public class NumberPicker extends LinearLayout { @Override public boolean onTouchEvent(MotionEvent ev) { + if (!isEnabled()) { + return false; + } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } @@ -702,12 +720,6 @@ public class NumberPicker extends LinearLayout { } } - /** - * Set the enabled state of this view. The interpretation of the enabled - * state varies by subclass. - * - * @param enabled True if this view is enabled, false otherwise. - */ @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); @@ -716,9 +728,6 @@ public class NumberPicker extends LinearLayout { mInputText.setEnabled(enabled); } - /** - * Scrolls the selector with the given <code>vertical offset</code>. - */ @Override public void scrollBy(int x, int y) { int[] selectorIndices = getSelectorIndices(); @@ -734,11 +743,11 @@ public class NumberPicker extends LinearLayout { mSelectorElementHeight = mTextSize + selectorTextGapHeight; } - if (!mWrapSelector && y > 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mStart) { + if (!mWrapSelectorWheel && y > 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { mCurrentScrollOffset = mInitialScrollOffset; return; } - if (!mWrapSelector && y < 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mEnd) { + if (!mWrapSelectorWheel && y < 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) { mCurrentScrollOffset = mInitialScrollOffset; return; } @@ -747,7 +756,7 @@ public class NumberPicker extends LinearLayout { mCurrentScrollOffset -= mSelectorElementHeight; decrementSelectorIndices(selectorIndices); changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); - if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mStart) { + if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { mCurrentScrollOffset = mInitialScrollOffset; } } @@ -755,7 +764,7 @@ public class NumberPicker extends LinearLayout { mCurrentScrollOffset += mSelectorElementHeight; incrementScrollSelectorIndices(selectorIndices); changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); - if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mEnd) { + if (selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) { mCurrentScrollOffset = mInitialScrollOffset; } } @@ -769,16 +778,16 @@ public class NumberPicker extends LinearLayout { /** * Sets the listener to be notified on change of the current value. * - * @param onChangeListener The listener. + * @param onValueChangedListener The listener. */ - public void setOnChangeListener(OnChangeListener onChangeListener) { - mOnChangeListener = onChangeListener; + public void setOnValueChangedListener(OnValueChangedListener onValueChangedListener) { + mOnValueChangedListener = onValueChangedListener; } /** * Set listener to be notified for scroll state changes. * - * @param onScrollListener the callback, should not be null. + * @param onScrollListener The listener. */ public void setOnScrollListener(OnScrollListener onScrollListener) { mOnScrollListener = onScrollListener; @@ -787,165 +796,216 @@ public class NumberPicker extends LinearLayout { /** * Set the formatter to be used for formatting the current value. * <p> - * Note: If you have provided alternative values for the selected positons - * this formatter is never invoked. + * Note: If you have provided alternative values for the values this + * formatter is never invoked. * </p> * - * @param formatter the formatter object. If formatter is null, - * String.valueOf() will be used. + * @param formatter The formatter object. If formatter is <code>null</code>, + * {@link String#valueOf(int)} will be used. * - * @see #setRange(int, int, String[]) + * @see #setDisplayedValues(String[]) */ public void setFormatter(Formatter formatter) { + if (formatter == mFormatter) { + return; + } mFormatter = formatter; - resetSelectorIndices(); + resetSelectorWheelIndices(); } /** - * Set the range of numbers allowed for the number picker. The current value - * will be automatically set to the start. - * - * @param start the start of the range (inclusive) - * @param end the end of the range (inclusive) - */ - public void setRange(int start, int end) { - setRange(start, end, null); - } - - /** - * Set the range of numbers allowed for the number picker. The current value - * will be automatically set to the start. Also provide a mapping for values - * used to display to the user instead of the numbers in the range. - * - * @param start The start of the range (inclusive). - * @param end The end of the range (inclusive). - * @param displayedValues The values displayed to the user. - */ - public void setRange(int start, int end, String[] displayedValues) { - boolean wrapSelector = (end - start) >= mSelectorIndices.length; - setRange(start, end, displayedValues, wrapSelector); - } - - /** - * Set the range of numbers allowed for the number picker. The current value - * will be automatically set to the start. Also provide a mapping for values - * used to display to the user. + * Set the current value for the number picker. * <p> - * Note: The <code>wrapSelectorWheel</code> argument is ignored if the range - * (difference between <code>start</code> and <code>end</code>) us less than - * five since this is the number of values shown by the selector wheel. + * If the argument is less than the {@link NumberPicker#getMinValue()} and + * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the + * current value is set to the {@link NumberPicker#getMinValue()} value. + * </p> + * <p> + * If the argument is less than the {@link NumberPicker#getMinValue()} and + * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the + * current value is set to the {@link NumberPicker#getMaxValue()} value. + * </p> + * <p> + * If the argument is less than the {@link NumberPicker#getMaxValue()} and + * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the + * current value is set to the {@link NumberPicker#getMaxValue()} value. + * </p> + * <p> + * If the argument is less than the {@link NumberPicker#getMaxValue()} and + * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the + * current value is set to the {@link NumberPicker#getMinValue()} value. * </p> * - * @param start the start of the range (inclusive) - * @param end the end of the range (inclusive) - * @param displayedValues the values displayed to the user. - * @param wrapSelectorWheel Whether to wrap the selector wheel. - * + * @param value The current value. * @see #setWrapSelectorWheel(boolean) + * @see #setMinValue(int) + * @see #setMaxValue(int) */ - public void setRange(int start, int end, String[] displayedValues, boolean wrapSelectorWheel) { - if (start == mStart && end == mEnd) { + public void setValue(int value) { + if (mValue == value) { return; } - - if (start < 0 || end < 0) { - throw new IllegalArgumentException("start and end must be > 0"); + if (value < mMinValue) { + value = mWrapSelectorWheel ? mMaxValue : mMinValue; } - - mDisplayedValues = displayedValues; - mStart = start; - mEnd = end; - mCurrent = start; - - setWrapSelectorWheel(wrapSelectorWheel); - updateInputTextView(); - - if (displayedValues != null) { - // Allow text entry rather than strictly numeric entry. - mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); - } else { - mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER); + if (value > mMaxValue) { + value = mWrapSelectorWheel ? mMinValue : mMaxValue; } - - resetSelectorIndices(); + mValue = value; + updateInputTextView(); + updateIncrementAndDecrementButtonsVisibilityState(); } /** - * Set the current value for the number picker. + * Gets whether the selector wheel wraps when reaching the min/max value. * - * @param current the current value the start of the range (inclusive) + * @return True if the selector wheel wraps. * - * @throws IllegalArgumentException when current is not within the range of - * of the number picker. + * @see #getMinValue() + * @see #getMaxValue() */ - public void setCurrent(int current) { - if (mCurrent == current) { - return; - } - if (current < mStart || current > mEnd) { - throw new IllegalArgumentException("current should be >= start and <= end"); - } - mCurrent = current; - updateInputTextView(); - updateIncrementAndDecrementButtonsVisibilityState(); + public boolean getWrapSelectorWheel() { + return mWrapSelectorWheel; } /** - * Sets whether the selector wheel shown during flinging/scrolling should wrap - * around the beginning and end values. By default if the range is more than - * five (the number of items shown on the selector wheel) the selector wheel - * wrapping is enabled. + * Sets whether the selector wheel shown during flinging/scrolling should + * wrap around the {@link NumberPicker#getMinValue()} and + * {@link NumberPicker#getMaxValue()} values. + * <p> + * By default if the range (max - min) is more than five (the number of + * items shown on the selector wheel) the selector wheel wrapping is + * enabled. + * </p> * * @param wrapSelector Whether to wrap. */ public void setWrapSelectorWheel(boolean wrapSelector) { - if (wrapSelector && (mEnd - mStart) < mSelectorIndices.length) { + if (wrapSelector && (mMaxValue - mMinValue) < mSelectorIndices.length) { throw new IllegalStateException("Range less than selector items count."); } - if (wrapSelector != mWrapSelector) { + if (wrapSelector != mWrapSelectorWheel) { // force the selector indices array to be reinitialized mSelectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] = Integer.MAX_VALUE; - mWrapSelector = wrapSelector; + mWrapSelectorWheel = wrapSelector; } } /** * Sets the speed at which the numbers be incremented and decremented when * the up and down buttons are long pressed respectively. + * <p> + * The default value is 300 ms. + * </p> * * @param intervalMillis The speed (in milliseconds) at which the numbers - * will be incremented and decremented (default 300ms). + * will be incremented and decremented. */ public void setOnLongPressUpdateInterval(long intervalMillis) { mLongPressUpdateInterval = intervalMillis; } /** - * Returns the current value of the NumberPicker. + * Returns the value of the picker. + * + * @return The value. + */ + public int getValue() { + return mValue; + } + + /** + * Returns the min value of the picker. + * + * @return The min value + */ + public int getMinValue() { + return mMinValue; + } + + /** + * Sets the min value of the picker. + * + * @param minValue The min value. + */ + public void setMinValue(int minValue) { + if (mMinValue == minValue) { + return; + } + if (minValue < 0) { + throw new IllegalArgumentException("minValue must be >= 0"); + } + mMinValue = minValue; + if (mMinValue > mValue) { + mValue = mMinValue; + } + boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; + setWrapSelectorWheel(wrapSelectorWheel); + resetSelectorWheelIndices(); + updateInputTextView(); + updateIncrementAndDecrementButtonsVisibilityState(); + } + + /** + * Returns the max value of the picker. * - * @return the current value. + * @return The max value. */ - public int getCurrent() { - return mCurrent; + public int getMaxValue() { + return mMaxValue; + } + + /** + * Sets the max value of the picker. + * + * @param maxValue The max value. + */ + public void setMaxValue(int maxValue) { + if (mMaxValue == maxValue) { + return; + } + if (maxValue < 0) { + throw new IllegalArgumentException("maxValue must be >= 0"); + } + mMaxValue = maxValue; + if (mMaxValue < mValue) { + mValue = mMaxValue; + } + boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length; + setWrapSelectorWheel(wrapSelectorWheel); + resetSelectorWheelIndices(); + updateInputTextView(); + updateIncrementAndDecrementButtonsVisibilityState(); } /** - * Returns the range lower value of the NumberPicker. + * Gets the values to be displayed instead of string values. * - * @return The lower number of the range. + * @return The displayed values. */ - public int getRangeStart() { - return mStart; + public String[] getDisplayedValues() { + return mDisplayedValues; } /** - * Returns the range end value of the NumberPicker. + * Sets the values to be displayed. * - * @return The upper number of the range. + * @param displayedValues The displayed values. */ - public int getRangeEnd() { - return mEnd; + public void setDisplayedValues(String[] displayedValues) { + if (mDisplayedValues == displayedValues) { + return; + } + mDisplayedValues = displayedValues; + if (mDisplayedValues != null) { + // Allow text entry rather than strictly numeric entry. + mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + } else { + mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER); + } + updateInputTextView(); + resetSelectorWheelIndices(); } @Override @@ -1018,7 +1078,7 @@ public class NumberPicker extends LinearLayout { * Resets the selector indices and clear the cached * string representation of these indices. */ - private void resetSelectorIndices() { + private void resetSelectorWheelIndices() { mSelectorIndexToStringCache.clear(); int[] selectorIdices = getSelectorIndices(); for (int i = 0; i < selectorIdices.length; i++) { @@ -1035,15 +1095,15 @@ public class NumberPicker extends LinearLayout { * @param current the new value of the NumberPicker */ private void changeCurrent(int current) { - if (mCurrent == current) { + if (mValue == current) { return; } // Wrap around the values if we go past the start or end - if (mWrapSelector) { + if (mWrapSelectorWheel) { current = getWrappedSelectorIndex(current); } - int previous = mCurrent; - setCurrent(current); + int previous = mValue; + setValue(current); notifyChange(previous, current); } @@ -1108,7 +1168,7 @@ public class NumberPicker extends LinearLayout { mPreviousScrollerY = 0; Scroller flingScroller = mFlingScroller; - if (mWrapSelector) { + if (mWrapSelectorWheel) { if (velocityY > 0) { flingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE); } else { @@ -1116,10 +1176,10 @@ public class NumberPicker extends LinearLayout { } } else { if (velocityY > 0) { - int maxY = mTextSize * (mCurrent - mStart); + int maxY = mTextSize * (mValue - mMinValue); flingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, maxY); } else { - int startY = mTextSize * (mEnd - mCurrent); + int startY = mTextSize * (mMaxValue - mValue); int maxY = startY; flingScroller.fling(0, startY, 0, velocityY, 0, 0, 0, maxY); } @@ -1153,12 +1213,12 @@ public class NumberPicker extends LinearLayout { * Updates the visibility state of the increment and decrement buttons. */ private void updateIncrementAndDecrementButtonsVisibilityState() { - if (mWrapSelector || mCurrent < mEnd) { + if (mWrapSelectorWheel || mValue < mMaxValue) { mIncrementButton.setVisibility(VISIBLE); } else { mIncrementButton.setVisibility(INVISIBLE); } - if (mWrapSelector || mCurrent > mStart) { + if (mWrapSelectorWheel || mValue > mMinValue) { mDecrementButton.setVisibility(VISIBLE); } else { mDecrementButton.setVisibility(INVISIBLE); @@ -1170,11 +1230,11 @@ public class NumberPicker extends LinearLayout { * the middle one. */ private int[] getSelectorIndices() { - int current = getCurrent(); + int current = getValue(); if (mSelectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] != current) { for (int i = 0; i < mSelectorIndices.length; i++) { int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX); - if (mWrapSelector) { + if (mWrapSelectorWheel) { selectorIndex = getWrappedSelectorIndex(selectorIndex); } mSelectorIndices[i] = selectorIndex; @@ -1188,10 +1248,10 @@ public class NumberPicker extends LinearLayout { * @return The wrapped index <code>selectorIndex</code> value. */ private int getWrappedSelectorIndex(int selectorIndex) { - if (selectorIndex > mEnd) { - return mStart + (selectorIndex - mEnd) % (mEnd - mStart) - 1; - } else if (selectorIndex < mStart) { - return mEnd - (mStart - selectorIndex) % (mEnd - mStart) + 1; + if (selectorIndex > mMaxValue) { + return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1; + } else if (selectorIndex < mMinValue) { + return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1; } return selectorIndex; } @@ -1205,8 +1265,8 @@ public class NumberPicker extends LinearLayout { selectorIndices[i] = selectorIndices[i + 1]; } int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1; - if (mWrapSelector && nextScrollSelectorIndex > mEnd) { - nextScrollSelectorIndex = mStart; + if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) { + nextScrollSelectorIndex = mMinValue; } selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex; ensureCachedScrollSelectorValue(nextScrollSelectorIndex); @@ -1221,8 +1281,8 @@ public class NumberPicker extends LinearLayout { selectorIndices[i] = selectorIndices[i - 1]; } int nextScrollSelectorIndex = selectorIndices[1] - 1; - if (mWrapSelector && nextScrollSelectorIndex < mStart) { - nextScrollSelectorIndex = mEnd; + if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) { + nextScrollSelectorIndex = mMaxValue; } selectorIndices[0] = nextScrollSelectorIndex; ensureCachedScrollSelectorValue(nextScrollSelectorIndex); @@ -1239,11 +1299,11 @@ public class NumberPicker extends LinearLayout { if (scrollSelectorValue != null) { return; } - if (selectorIndex < mStart || selectorIndex > mEnd) { + if (selectorIndex < mMinValue || selectorIndex > mMaxValue) { scrollSelectorValue = ""; } else { if (mDisplayedValues != null) { - int displayedValueIndex = selectorIndex - mStart; + int displayedValueIndex = selectorIndex - mMinValue; scrollSelectorValue = mDisplayedValues[displayedValueIndex]; } else { scrollSelectorValue = formatNumber(selectorIndex); @@ -1253,7 +1313,7 @@ public class NumberPicker extends LinearLayout { } private String formatNumber(int value) { - return (mFormatter != null) ? mFormatter.toString(value) : String.valueOf(value); + return (mFormatter != null) ? mFormatter.format(value) : String.valueOf(value); } private void validateInputTextView(View v) { @@ -1281,9 +1341,9 @@ public class NumberPicker extends LinearLayout { * number. */ if (mDisplayedValues == null) { - mInputText.setText(formatNumber(mCurrent)); + mInputText.setText(formatNumber(mValue)); } else { - mInputText.setText(mDisplayedValues[mCurrent - mStart]); + mInputText.setText(mDisplayedValues[mValue - mMinValue]); } mInputText.setSelection(mInputText.getText().length()); } @@ -1293,8 +1353,8 @@ public class NumberPicker extends LinearLayout { * NumberPicker. */ private void notifyChange(int previous, int current) { - if (mOnChangeListener != null) { - mOnChangeListener.onChange(this, previous, mCurrent); + if (mOnValueChangedListener != null) { + mOnValueChangedListener.onValueChange(this, previous, mValue); } } @@ -1342,7 +1402,7 @@ public class NumberPicker extends LinearLayout { // Don't force the user to type in jan when ja will do value = value.toLowerCase(); if (mDisplayedValues[i].toLowerCase().startsWith(value)) { - return mStart + i; + return mMinValue + i; } } @@ -1357,7 +1417,7 @@ public class NumberPicker extends LinearLayout { // Ignore as if it's not a number we don't care } } - return mStart; + return mMinValue; } /** @@ -1429,7 +1489,7 @@ public class NumberPicker extends LinearLayout { * allowed. We have to allow less than min as the user might * want to delete some numbers and then type a new number. */ - if (val > mEnd) { + if (val > mMaxValue) { return ""; } else { return filtered; @@ -1493,7 +1553,7 @@ public class NumberPicker extends LinearLayout { } public void run() { - changeCurrent(mCurrent + mUpdateStep); + changeCurrent(mValue + mUpdateStep); postDelayed(this, mLongPressUpdateInterval); } } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 203b637..107ea07 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -25,7 +25,7 @@ import android.os.Parcelable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; -import android.widget.NumberPicker.OnChangeListener; +import android.widget.NumberPicker.OnValueChangedListener; import java.text.DateFormatSymbols; import java.util.Calendar; @@ -68,13 +68,15 @@ public class TimePicker extends FrameLayout { private boolean mIsAm; // ui components - private final NumberPicker mHourPicker; - private final NumberPicker mMinutePicker; - private final NumberPicker mAmPmPicker; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; private final TextView mDivider; private final String[] mAmPmStrings; + private boolean mIsEnabled; + // callbacks private OnTimeChangedListener mOnTimeChangedListener; @@ -109,9 +111,9 @@ public class TimePicker extends FrameLayout { true); // hour - mHourPicker = (NumberPicker) findViewById(R.id.hour); - mHourPicker.setOnChangeListener(new NumberPicker.OnChangeListener() { - public void onChange(NumberPicker spinner, int oldVal, int newVal) { + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangedListener() { + public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { mCurrentHour = newVal; if (!mIs24HourView) { // adjust from [1-12] to [0-11] internally, with the times @@ -133,21 +135,22 @@ public class TimePicker extends FrameLayout { mDivider.setText(R.string.time_picker_separator); // digits of minute - mMinutePicker = (NumberPicker) findViewById(R.id.minute); - mMinutePicker.setRange(0, 59); - mMinutePicker.setOnLongPressUpdateInterval(100); - mMinutePicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); - mMinutePicker.setOnChangeListener(new NumberPicker.OnChangeListener() { - public void onChange(NumberPicker spinner, int oldVal, int newVal) { + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(0); + mMinuteSpinner.setMaxValue(59); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangedListener() { + public void onValueChange(NumberPicker spinner, int oldVal, int newVal) { mCurrentMinute = newVal; onTimeChanged(); } }); // am/pm - mAmPmPicker = (NumberPicker) findViewById(R.id.amPm); - mAmPmPicker.setOnChangeListener(new OnChangeListener() { - public void onChange(NumberPicker picker, int oldVal, int newVal) { + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); + mAmPmSpinner.setOnValueChangedListener(new OnValueChangedListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { picker.requestFocus(); if (mIsAm) { // Currently AM switching to PM @@ -187,10 +190,19 @@ public class TimePicker extends FrameLayout { @Override public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; + } super.setEnabled(enabled); - mMinutePicker.setEnabled(enabled); - mHourPicker.setEnabled(enabled); - mAmPmPicker.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; } /** @@ -228,6 +240,7 @@ public class TimePicker extends FrameLayout { dest.writeInt(mMinute); } + @SuppressWarnings("unused") public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { @@ -313,7 +326,7 @@ public class TimePicker extends FrameLayout { @Override public int getBaseline() { - return mHourPicker.getBaseline(); + return mHourSpinner.getBaseline(); } /** @@ -329,22 +342,26 @@ public class TimePicker extends FrameLayout { currentHour = 12; } } - mHourPicker.setCurrent(currentHour); + mHourSpinner.setValue(currentHour); mIsAm = mCurrentHour < 12; - mAmPmPicker.setCurrent(mIsAm ? Calendar.AM : Calendar.PM); + mAmPmSpinner.setValue(mIsAm ? Calendar.AM : Calendar.PM); onTimeChanged(); } private void configurePickerRanges() { if (mIs24HourView) { - mHourPicker.setRange(0, 23); - mHourPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); - mAmPmPicker.setVisibility(View.GONE); + mHourSpinner.setMinValue(0); + mHourSpinner.setMaxValue(23); + mHourSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + mAmPmSpinner.setVisibility(View.GONE); } else { - mHourPicker.setRange(1, 12); - mHourPicker.setFormatter(null); - mAmPmPicker.setVisibility(View.VISIBLE); - mAmPmPicker.setRange(0, 1, mAmPmStrings); + mHourSpinner.setMinValue(1); + mHourSpinner.setMaxValue(12); + mHourSpinner.setFormatter(null); + mAmPmSpinner.setVisibility(View.VISIBLE); + mAmPmSpinner.setMinValue(0); + mAmPmSpinner.setMaxValue(1); + mAmPmSpinner.setDisplayedValues(mAmPmStrings); } } @@ -358,7 +375,7 @@ public class TimePicker extends FrameLayout { * Set the state of the spinners appropriate to the current minute. */ private void updateMinuteDisplay() { - mMinutePicker.setCurrent(mCurrentMinute); + mMinuteSpinner.setValue(mCurrentMinute); onTimeChanged(); } } diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java index 6f6b40b..e32c62d 100644 --- a/core/java/com/android/internal/app/LocalePicker.java +++ b/core/java/com/android/internal/app/LocalePicker.java @@ -82,6 +82,11 @@ public class LocalePicker extends ListFragment { * {@link LocaleInfo#label}. */ public static ArrayAdapter<LocaleInfo> constructAdapter(Context context) { + return constructAdapter(context, R.layout.locale_picker_item, R.id.locale); + } + + public static ArrayAdapter<LocaleInfo> constructAdapter(Context context, + int layoutId, int fieldId) { final Resources resources = context.getResources(); final String[] locales = context.getAssets().getLocales(); final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes); @@ -149,8 +154,6 @@ public class LocalePicker extends ListFragment { localeInfos[i] = preprocess[i]; } Arrays.sort(localeInfos); - final int layoutId = R.layout.locale_picker_item; - final int fieldId = R.id.locale; return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 8eeed3d..e9566ad 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -123,6 +123,7 @@ LOCAL_SRC_FILES:= \ android_media_ToneGenerator.cpp \ android_hardware_Camera.cpp \ android_hardware_SensorManager.cpp \ + android_hardware_UsbManager.cpp \ android_debug_JNITest.cpp \ android_util_FileObserver.cpp \ android/opengl/poly_clip.cpp.arm \ @@ -200,7 +201,8 @@ LOCAL_SHARED_LIBRARIES := \ libmedia \ libwpa_client \ libjpeg \ - libnfc_ndef + libnfc_ndef \ + libusbhost ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 2dfebe5..961bc1f1 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -77,8 +77,8 @@ extern int register_android_opengl_jni_GLES11Ext(JNIEnv* env); extern int register_android_opengl_jni_GLES20(JNIEnv* env); extern int register_android_hardware_Camera(JNIEnv *env); - extern int register_android_hardware_SensorManager(JNIEnv *env); +extern int register_android_hardware_UsbManager(JNIEnv *env); extern int register_android_media_AudioRecord(JNIEnv *env); extern int register_android_media_AudioSystem(JNIEnv *env); @@ -1266,6 +1266,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_SensorManager), + REG_JNI(register_android_hardware_UsbManager), REG_JNI(register_android_media_AudioRecord), REG_JNI(register_android_media_AudioSystem), REG_JNI(register_android_media_AudioTrack), diff --git a/core/jni/android_hardware_UsbManager.cpp b/core/jni/android_hardware_UsbManager.cpp new file mode 100644 index 0000000..8f32abf --- /dev/null +++ b/core/jni/android_hardware_UsbManager.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "jni.h" +#include "JNIHelp.h" +#include "android_runtime/AndroidRuntime.h" + +#include <usbhost/usbhost.h> + +#include <stdio.h> + +using namespace android; + +static jint android_hardware_UsbManager_get_device_id(JNIEnv *env, jobject clazz, jstring name) +{ + const char *nameStr = env->GetStringUTFChars(name, NULL); + int id = usb_device_get_unique_id_from_name(nameStr); + env->ReleaseStringUTFChars(name, nameStr); + return id; +} + +static jstring android_hardware_UsbManager_get_device_name(JNIEnv *env, jobject clazz, jint id) +{ + char* name = usb_device_get_name_from_unique_id(id); + jstring result = env->NewStringUTF(name); + free(name); + return result; +} + +static JNINativeMethod method_table[] = { + { "native_get_device_id", "(Ljava/lang/String;)I", + (void*)android_hardware_UsbManager_get_device_id }, + { "native_get_device_name", "(I)Ljava/lang/String;", + (void*)android_hardware_UsbManager_get_device_name }, +}; + +int register_android_hardware_UsbManager(JNIEnv *env) +{ + jclass clazz = env->FindClass("android/hardware/UsbManager"); + if (clazz == NULL) { + LOGE("Can't find android/hardware/UsbManager"); + return -1; + } + + return AndroidRuntime::registerNativeMethods(env, "android/hardware/UsbManager", + method_table, NELEM(method_table)); +} + diff --git a/core/res/res/layout/day_picker.xml b/core/res/res/layout/calendar_view.xml index a030df8..176bb8b 100644 --- a/core/res/res/layout/day_picker.xml +++ b/core/res/res/layout/calendar_view.xml @@ -48,43 +48,37 @@ <TextView android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - style="?android:attr/dayPickerWeekDayViewStyle" /> + android:gravity="center" /> + <TextView android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - style="?android:attr/dayPickerWeekDayViewStyle" /> + android:gravity="center" /> <TextView android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - style="?android:attr/dayPickerWeekDayViewStyle" /> + android:gravity="center" /> <TextView android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - style="?android:attr/dayPickerWeekDayViewStyle" /> + android:gravity="center" /> <TextView android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - style="?android:attr/dayPickerWeekDayViewStyle" /> + android:gravity="center" /> <TextView android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - style="?android:attr/dayPickerWeekDayViewStyle" /> + android:gravity="center" /> <TextView android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:gravity="center" - style="?android:attr/dayPickerWeekDayViewStyle" /> + android:gravity="center" /> </LinearLayout> diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker.xml index 5c023ee..e9663b1 100644 --- a/core/res/res/layout/date_picker.xml +++ b/core/res/res/layout/date_picker.xml @@ -32,8 +32,8 @@ <LinearLayout android:id="@+id/pickers" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginRight="15dip" - android:layout_weight="0.5" + android:layout_marginRight="22dip" + android:layout_weight="1" android:orientation="horizontal" android:gravity="center"> @@ -42,10 +42,8 @@ android:id="@+id/month" android:layout_width="48dip" android:layout_height="wrap_content" - android:layout_marginLeft="15dip" - android:layout_marginRight="15dip" - android:layout_marginTop="35dip" - android:layout_marginBottom="35dip" + android:layout_marginLeft="22dip" + android:layout_marginRight="22dip" android:focusable="true" android:focusableInTouchMode="true" /> @@ -55,10 +53,8 @@ android:id="@+id/day" android:layout_width="48dip" android:layout_height="wrap_content" - android:layout_marginLeft="15dip" - android:layout_marginRight="15dip" - android:layout_marginTop="35dip" - android:layout_marginBottom="35dip" + android:layout_marginLeft="22dip" + android:layout_marginRight="22dip" android:focusable="true" android:focusableInTouchMode="true" /> @@ -68,25 +64,23 @@ android:id="@+id/year" android:layout_width="48dip" android:layout_height="wrap_content" - android:layout_marginLeft="15dip" - android:layout_marginRight="15dip" - android:layout_marginTop="35dip" - android:layout_marginBottom="35dip" + android:layout_marginLeft="22dip" + android:layout_marginRight="22dip" android:focusable="true" android:focusableInTouchMode="true" /> </LinearLayout> - <!-- mini-month day-picker --> - <DayPicker - android:id="@+id/mini_month_day_picker" - android:layout_width="200dip" - android:layout_height="230dip" - android:layout_marginLeft="15dip" - android:layout_weight="0.5" - android:focusable="true" - android:focusableInTouchMode="true" - /> + <!-- calendar view --> + <CalendarView + android:id="@+id/calendar_view" + android:layout_width="245dip" + android:layout_height="280dip" + android:layout_marginLeft="22dip" + android:layout_weight="1" + android:focusable="true" + android:focusableInTouchMode="true" + /> </LinearLayout> diff --git a/core/res/res/layout/date_picker_dialog.xml b/core/res/res/layout/date_picker_dialog.xml index 148e192..004d52a 100644 --- a/core/res/res/layout/date_picker_dialog.xml +++ b/core/res/res/layout/date_picker_dialog.xml @@ -20,5 +20,6 @@ <DatePicker xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/datePicker" android:layout_gravity="center_horizontal" - android:layout_width="250dip" - android:layout_height="600dip"/> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> diff --git a/core/res/res/layout/time_picker.xml b/core/res/res/layout/time_picker.xml index bf81c18..382b2f6 100644 --- a/core/res/res/layout/time_picker.xml +++ b/core/res/res/layout/time_picker.xml @@ -30,9 +30,8 @@ android:id="@+id/hour" android:layout_width="48dip" android:layout_height="wrap_content" - android:layout_marginRight="13dip" - android:layout_marginTop="35dip" - android:layout_marginBottom="35dip" + android:layout_marginLeft="22dip" + android:layout_marginRight="20dip" android:focusable="true" android:focusableInTouchMode="true" /> @@ -50,10 +49,8 @@ android:id="@+id/minute" android:layout_width="48dip" android:layout_height="wrap_content" - android:layout_marginLeft="13dip" - android:layout_marginRight="15dip" - android:layout_marginTop="35dip" - android:layout_marginBottom="35dip" + android:layout_marginLeft="20dip" + android:layout_marginRight="22dip" android:focusable="true" android:focusableInTouchMode="true" /> @@ -63,9 +60,8 @@ android:id="@+id/amPm" android:layout_width="48dip" android:layout_height="wrap_content" - android:layout_marginLeft="15dip" - android:layout_marginTop="35dip" - android:layout_marginBottom="35dip" + android:layout_marginLeft="22dip" + android:layout_marginRight="22dip" android:focusable="true" android:focusableInTouchMode="true" /> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 12ff047..f6899ad 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -504,21 +504,17 @@ <!-- Default PopupMenu style. --> <attr name="popupMenuStyle" format="reference" /> - <!-- @hide NumberPicker up button style --> + <!-- NumberPicker style. --> + <attr name="numberPickerStyle" format="reference" /> + <!-- NumberPicker up button style. --> <attr name="numberPickerUpButtonStyle" format="reference" /> - <!-- @hide NumberPicker down button style --> + <!-- NumberPicker down button style. --> <attr name="numberPickerDownButtonStyle" format="reference" /> - <!-- @hide NumberPicker input text style --> + <!-- NumberPicker input text style. --> <attr name="numberPickerInputTextStyle" format="reference" /> - <!-- @hide NumberPicker the fading edge length of the selector wheel --> - <attr name="numberPickerStyle" format="reference" /> - - <!-- @hide DayPicker$WeekView style--> - <attr name="dayPickerWeekViewStyle" format="reference" /> - - <!-- @hide DayPickerDayView style--> - <attr name="dayPickerWeekDayViewStyle" format="reference" /> + <!-- The CalndarView style. --> + <attr name="calendarViewStyle" format="reference" /> <!-- Fast scroller styles --> <eat-comment /> @@ -2826,6 +2822,14 @@ <attr name="startYear" format="integer" /> <!-- The last year (inclusive), for example "2010". --> <attr name="endYear" format="integer" /> + <!-- Whether the spinners are shown. --> + <attr name="spinnersShown" format="boolean" /> + <!-- Whether the calendar view is shown. --> + <attr name="calendarViewShown" format="boolean" /> + <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. --> + <attr name="minDate" format="string" /> + <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. --> + <attr name="maxDate" format="string" /> </declare-styleable> <declare-styleable name="TwoLineListItem"> @@ -2994,27 +2998,38 @@ <attr name="minorWeightMax" format="float" /> </declare-styleable> - <!-- @hide --> - <declare-styleable name="DayPickerWeekView"> - <attr name="height" /> - <attr name="weekStartDay" format="integer|reference" /> - <attr name="weekDayCount" format="integer|reference" /> - <attr name="showWeekNumber" format="boolean|reference" /> - <attr name="weekSeperatorWidth" format="dimension" /> - <attr name="textSize" /> - <attr name="weekDayPadding" format="dimension" /> - <attr name="selectedDayLineWidth" format="dimension" /> - <attr name="selectionBackgroundColor" format="color|reference" /> + <declare-styleable name="CalendarView"> + <!-- The first day of week according to {@link java.util.Calendar}. --> + <attr name="firstDayOfWeek" format="integer" /> + <!-- Whether do show week numbers. --> + <attr name="showWeekNumber" format="boolean" /> + <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. --> + <attr name="minDate" /> + <!-- The minimal date shown by this calendar view in mm/dd/yyyy format. --> + <attr name="maxDate" /> + <!-- The number of weeks to be shown. --> + <attr name="shownWeekCount" format="integer"/> + <!-- The background color for the selected week. --> + <attr name="selectedWeekBackgroundColor" format="color|reference" /> + <!-- The color for the dates of the selected month. --> <attr name="focusedMonthDateColor" format="color|reference" /> - <attr name="otherMonthDateColor" format="color|reference" /> + <!-- The color for the dates of an unfocused month. --> + <attr name="unfocusedMonthDateColor" format="color|reference" /> + <!-- The color for the week numbers. --> <attr name="weekNumberColor" format="color|reference" /> - <attr name="gridLinesColor" format="color|reference" /> - <attr name="selectedDayLine" format="reference" /> + <!-- The color for the sepatator line between weeks. --> + <attr name="weekSeparatorLineColor" format="color|reference" /> + <!-- Drawable for the vertical bar shown at the beggining and at the end of a selected date. --> + <attr name="selectedDateVerticalBar" format="reference" /> + <!-- The text appearance for the week day abbreviation of the calendar header. --> + <attr name="weekDayTextAppearance" format="reference" /> + <!-- The text appearance for the calendar dates. --> + <attr name="dateTextAppearance" format="reference" /> </declare-styleable> - <!-- @hide --> <declare-styleable name="NumberPicker"> <attr name="orientation" /> + <!-- Color for the solid color background if such for optimized rendering. --> <attr name="solidColor" format="color|reference" /> </declare-styleable> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8edea0d..590baf1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -497,6 +497,9 @@ <!-- Enables SIP on WIFI only --> <bool name="config_sip_wifi_only">true</bool> + <!-- Enables built-in SIP phone capability --> + <bool name="config_built_in_sip_phone">false</bool> + <!-- Boolean indicating if restoring network selection should be skipped --> <!-- The restoring is handled by modem if it is true--> <bool translatable="false" name="skip_restoring_network_selection">false</bool> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 5aed668..9f1b9a1 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1409,6 +1409,22 @@ <public type="attr" name="fastScrollOverlayPosition" /> <public type="attr" name="customTokens" /> <public type="attr" name="nextFocusForward" /> + <public type="attr" name="firstDayOfWeek" /> + <public type="attr" name="showWeekNumber" /> + <public type="attr" name="minDate" /> + <public type="attr" name="maxDate" /> + <public type="attr" name="shownWeekCount" /> + <public type="attr" name="selectedWeekBackgroundColor" /> + <public type="attr" name="focusedMonthDateColor" /> + <public type="attr" name="unfocusedMonthDateColor" /> + <public type="attr" name="weekNumberColor" /> + <public type="attr" name="weekSeparatorLineColor" /> + <public type="attr" name="selectedDateVerticalBar" /> + <public type="attr" name="weekDayTextAppearance" /> + <public type="attr" name="dateTextAppearance" /> + <public type="attr" name="solidColor" /> + <public type="attr" name="spinnersShown" /> + <public type="attr" name="calendarViewShown" /> <public type="anim" name="animator_fade_in" /> <public type="anim" name="animator_fade_out" /> @@ -1578,5 +1594,18 @@ <public type="style" name="Holo.Light.ButtonBar.AlertDialog" /> <public type="style" name="Holo.SegmentedButton" /> <public type="style" name="Holo.Light.SegmentedButton" /> + <public type="style" name="Widget.ImageButton.NumberPickerUpButton" /> + <public type="style" name="Widget.EditText.NumberPickerInputText" /> + <public type="style" name="Widget.ImageButton.NumberPickerDownButton" /> + <public type="style" name="Widget.Holo.ImageButton.NumberPickerUpButton" /> + <public type="style" name="Widget.Holo.EditText.NumberPickerInputText" /> + <public type="style" name="Widget.Holo.ImageButton.NumberPickerDownButton" /> + <public type="style" name="Widget.Holo.Light.ImageButton.NumberPickerUpButton" /> + <public type="style" name="Widget.Holo.Light.EditText.NumberPickerInputText" /> + <public type="style" name="Widget.Holo.Light.ImageButton.NumberPickerDownButton" /> + <public type="style" name="Widget.CalendarView" /> + <public type="style" name="Widget.Holo.CalendarView" /> + <public type="style" name="Widget.Holo.Light.CalendarView" /> <public type="string" name="selectTextMode" /> + </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 9c28922..f6c88e7 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -238,8 +238,7 @@ <item name="android:textColor">#ff272727</item> </style> - <!-- @hide --> - <style name="TextAppearance.Small.DayPickerWeekDayView"> + <style name="TextAppearance.Small.CalendarViewWeekDayView"> <item name="android:textStyle">bold</item> </style> @@ -473,25 +472,22 @@ <item name="android:background">@android:drawable/btn_default</item> </style> - <!-- @hide --> - <style name="Widget.DayPickerWeekView"> - <item name="android:height">26dip</item> - <item name="android:weekStartDay">1</item> - <item name="android:weekDayCount">7</item> + <style name="Widget.CalendarView"> <item name="android:showWeekNumber">true</item> - <item name="android:weekSeperatorWidth">1dip</item> - <item name="android:textSize">14dip</item> - <item name="android:weekDayPadding">0dip</item> - <item name="android:selectedDayLineWidth">6dip</item> - <item name="android:selectionBackgroundColor">#330099FF</item> + <item name="android:firstDayOfWeek">1</item> + <item name="android:minDate">01/01/1900</item> + <item name="android:maxDate">12/31/2100</item> + <item name="android:shownWeekCount">6</item> + <item name="android:selectedWeekBackgroundColor">#330099FF</item> <item name="android:focusedMonthDateColor">#FFFFFFFF</item> - <item name="android:otherMonthDateColor">#66FFFFFF</item> + <item name="android:unfocusedMonthDateColor">#66FFFFFF</item> <item name="android:weekNumberColor">#33FFFFFF</item> - <item name="android:gridLinesColor">#19FFFFFF</item> - <item name="selectedDayLine">@android:drawable/day_picker_week_view_dayline_holo</item> + <item name="android:weekSeparatorLineColor">#19FFFFFF</item> + <item name="android:selectedDateVerticalBar">@android:drawable/day_picker_week_view_dayline_holo</item> + <item name="android:weekDayTextAppearance">@android:style/TextAppearance.Small.CalendarViewWeekDayView</item> + <item name="android:dateTextAppearance">?android:attr/textAppearanceSmall</item> </style> - <!-- @hide --> <style name="Widget.NumberPicker"> <item name="android:orientation">vertical</item> <item name="android:fadingEdge">vertical</item> @@ -499,17 +495,14 @@ <item name="android:solidColor">@android:color/transparent</item> </style> - <!-- @hide --> <style name="Widget.ImageButton.NumberPickerUpButton"> <item name="android:background">@android:drawable/timepicker_up_btn</item> </style> - <!-- @hide --> <style name="Widget.ImageButton.NumberPickerDownButton"> <item name="android:background">@android:drawable/timepicker_down_btn</item> </style> - <!-- @hide --> <style name="Widget.EditText.NumberPickerInputText"> <item name="android:textAppearance">@style/TextAppearance.Large.Inverse.NumberPickerInputText</item> <item name="android:gravity">center</item> @@ -861,7 +854,6 @@ <item name="android:textStyle">bold</item> </style> - <!-- @hide --> <style name="TextAppearance.Large.Inverse.NumberPickerInputText"> <item name="android:textColor">@android:color/primary_text_light</item> <item name="android:textSize">30sp</item> @@ -1242,8 +1234,7 @@ <item name="android:textSize">18sp</item> </style> - <!-- @hide --> - <style name="TextAppearance.Holo.DayPickerWeekDayView" parent="TextAppearance.Small.DayPickerWeekDayView"> + <style name="TextAppearance.Holo.CalendarViewWeekDayView" parent="TextAppearance.Small.CalendarViewWeekDayView"> <item name="android:textColor">#505050</item> </style> @@ -1343,8 +1334,7 @@ <item name="android:textSize">18sp</item> </style> - <!-- @hide --> - <style name="TextAppearance.Holo.Light.DayPickerWeekDayView" parent="TextAppearance.Small.DayPickerWeekDayView"> + <style name="TextAppearance.Holo.Light.CalendarViewWeekDayView" parent="TextAppearance.Small.CalendarViewWeekDayView"> </style> <!-- Widget Styles --> @@ -1457,35 +1447,34 @@ <item name="android:listSelector">?android:attr/selectableItemBackground</item> </style> - <!-- @hide --> - <style name="Widget.Holo.DayPickerWeekView" parent="Widget.DayPickerWeekView"> - <item name="android:selectionBackgroundColor">#330099FF</item> + <style name="Widget.Holo.CalendarView" parent="Widget.CalendarView"> + <item name="android:selectedWeekBackgroundColor">#330099FF</item> <item name="android:focusedMonthDateColor">#FFFFFFFF</item> - <item name="android:otherMonthDateColor">#66FFFFFF</item> + <item name="android:unfocusedMonthDateColor">#66FFFFFF</item> <item name="android:weekNumberColor">#33FFFFFF</item> - <item name="android:gridLinesColor">#19FFFFFF</item> - <item name="selectedDayLine">@android:drawable/day_picker_week_view_dayline_holo</item> + <item name="android:weekSeparatorLineColor">#19FFFFFF</item> + <item name="android:selectedDateVerticalBar">@android:drawable/day_picker_week_view_dayline_holo</item> + <item name="android:weekDayTextAppearance">@android:style/TextAppearance.Holo.CalendarViewWeekDayView</item> </style> <style name="Widget.Holo.ImageButton" parent="Widget.ImageButton"> <item name="android:background">@android:drawable/btn_default_holo_dark</item> </style> - <!-- @hide --> <style name="Widget.Holo.ImageButton.NumberPickerUpButton"> <item name="android:background">@null</item> - <item name="android:paddingBottom">26sp</item> <item name="android:src">@android:drawable/timepicker_up_btn_holo_dark</item> + <item name="android:paddingTop">26dip</item> + <item name="android:paddingBottom">26dip</item> </style> - <!-- @hide --> <style name="Widget.Holo.ImageButton.NumberPickerDownButton"> <item name="android:background">@null</item> - <item name="android:paddingTop">26sp</item> <item name="android:src">@android:drawable/timepicker_down_btn_holo_dark</item> + <item name="android:paddingTop">26dip</item> + <item name="android:paddingBottom">26dip</item> </style> - <!-- @hide --> <style name="Widget.Holo.EditText.NumberPickerInputText"> <item name="android:paddingTop">13sp</item> <item name="android:paddingBottom">13sp</item> @@ -1831,27 +1820,23 @@ <item name="android:background">@android:drawable/btn_default_holo_light</item> </style> - <!-- @hide --> - <style name="Widget.Holo.Light.DayPickerWeekView" parent="Widget.DayPickerWeekView"> - <item name="android:selectionBackgroundColor">#330066ff</item> + <style name="Widget.Holo.Light.CalendarView" parent="Widget.CalendarView"> + <item name="android:selectedWeekBackgroundColor">#330066ff</item> <item name="android:focusedMonthDateColor">#FF000000</item> - <item name="android:otherMonthDateColor">#7F08002B</item> + <item name="android:unfocusedMonthDateColor">#7F08002B</item> <item name="android:weekNumberColor">#7F080021</item> - <item name="android:gridLinesColor">#7F08002A</item> - <item name="selectedDayLine">@android:drawable/day_picker_week_view_dayline_holo</item> + <item name="android:weekSeparatorLineColor">#7F08002A</item> + <item name="android:weekDayTextAppearance">@android:style/TextAppearance.Holo.Light.CalendarViewWeekDayView</item> </style> - <!-- @hide --> <style name="Widget.Holo.Light.ImageButton.NumberPickerUpButton" parent="Widget.Holo.ImageButton.NumberPickerUpButton"> <item name="android:src">@android:drawable/timepicker_up_btn_holo_light</item> </style> - <!-- @hide --> <style name="Widget.Holo.Light.ImageButton.NumberPickerDownButton" parent="Widget.Holo.ImageButton.NumberPickerDownButton"> <item name="android:src">@android:drawable/timepicker_down_btn_holo_light</item> </style> - <!-- @hide --> <style name="Widget.Holo.Light.EditText.NumberPickerInputText" parent="Widget.Holo.EditText.NumberPickerInputText"> </style> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 1bbe22e..43686bc 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -273,7 +273,6 @@ <item name="searchViewEditQuery">@android:drawable/ic_commit</item> <item name="searchViewEditQueryBackground">?attr/selectableItemBackground</item> - <!-- PreferenceFrameLayout attributes --> <item name="preferenceFrameLayoutStyle">@android:style/Widget.PreferenceFrameLayout</item> @@ -283,11 +282,8 @@ <item name="numberPickerInputTextStyle">@style/Widget.EditText.NumberPickerInputText</item> <item name="numberPickerStyle">@style/Widget.NumberPicker</item> - <!-- DayPicker$WeekView style--> - <item name="dayPickerWeekViewStyle">@style/Widget.DayPickerWeekView</item> - - <!-- DayPickerWeekDayView style--> - <item name="dayPickerWeekDayViewStyle">@style/TextAppearance.Small.DayPickerWeekDayView</item> + <!-- CalendarView style--> + <item name="calendarViewStyle">@style/Widget.CalendarView</item> <item name="fastScrollThumbDrawable">@android:drawable/scrollbar_handle_accelerated_anim2</item> <item name="fastScrollTrackDrawable">@null</item> @@ -937,11 +933,8 @@ <item name="numberPickerDownButtonStyle">@style/Widget.Holo.ImageButton.NumberPickerDownButton</item> <item name="numberPickerInputTextStyle">@style/Widget.Holo.EditText.NumberPickerInputText</item> - <!-- DayPicker$WeekView style--> - <item name="dayPickerWeekViewStyle">@style/Widget.Holo.DayPickerWeekView</item> - - <!-- DayPickerWeekDayView style--> - <item name="dayPickerWeekDayViewStyle">@style/TextAppearance.Holo.DayPickerWeekDayView</item> + <!-- CalendarView style--> + <item name="calendarViewStyle">@style/Widget.Holo.CalendarView</item> <item name="fastScrollThumbDrawable">@android:drawable/fastscroll_thumb_holo</item> <item name="fastScrollPreviewBackgroundLeft">@android:drawable/fastscroll_label_left_holo_dark</item> @@ -1194,11 +1187,8 @@ <item name="numberPickerDownButtonStyle">@style/Widget.Holo.Light.ImageButton.NumberPickerDownButton</item> <item name="numberPickerInputTextStyle">@style/Widget.Holo.Light.EditText.NumberPickerInputText</item> - <!-- DayPicker$WeekView style--> - <item name="dayPickerWeekViewStyle">@style/Widget.Holo.Light.DayPickerWeekView</item> - - <!-- DayPickerWeekDayView style--> - <item name="dayPickerWeekDayViewStyle">@style/TextAppearance.Holo.Light.DayPickerWeekDayView</item> + <!-- CalendarView style--> + <item name="calendarViewStyle">@style/Widget.Holo.Light.CalendarView</item> <item name="fastScrollThumbDrawable">@android:drawable/fastscroll_thumb_holo</item> <item name="fastScrollPreviewBackgroundLeft">@android:drawable/fastscroll_label_left_holo_light</item> |