diff options
Diffstat (limited to 'core/java/android/widget')
27 files changed, 1111 insertions, 389 deletions
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index eea97dc..271989a 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -546,6 +546,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private void initAbsListView() { // Setting focusable in touch mode will set the focusable property to true + setClickable(true); setFocusableInTouchMode(true); setWillNotDraw(false); setAlwaysDrawnWithCacheEnabled(false); @@ -1433,6 +1434,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * this is a long press. */ void keyPressed() { + if (!isEnabled() || !isClickable()) { + return; + } + Drawable selector = mSelector; Rect selectorRect = mSelectorRect; if (selector != null && (isFocused() || touchModeDrawsInPressedState()) @@ -1450,8 +1455,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te Drawable d = selector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { if (longClickable) { - ((TransitionDrawable) d).startTransition(ViewConfiguration - .getLongPressTimeout()); + ((TransitionDrawable) d).startTransition( + ViewConfiguration.getLongPressTimeout()); } else { ((TransitionDrawable) d).resetTransition(); } @@ -1732,18 +1737,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return false; + } + + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: - if (isPressed() && mSelectedPosition >= 0 && mAdapter != null && + if (!isEnabled()) { + return true; + } + if (isClickable() && isPressed() && + mSelectedPosition >= 0 && mAdapter != null && mSelectedPosition < mAdapter.getCount()) { + final View view = getChildAt(mSelectedPosition - mFirstPosition); performItemClick(view, mSelectedPosition, mSelectedRowId); setPressed(false); if (view != null) view.setPressed(false); return true; } + break; } return super.onKeyUp(keyCode, event); } @@ -1892,6 +1908,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te @Override public boolean onTouchEvent(MotionEvent ev) { + if (!isEnabled()) { + // A disabled view that is clickable still consumes the touch + // events, it just doesn't respond to them. + return isClickable() || isLongClickable(); + } + if (mFastScroller != null) { boolean intercepted = mFastScroller.onTouchEvent(ev); if (intercepted) { @@ -1974,7 +1996,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (y != mLastY) { deltaY -= mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; - trackMotionScroll(deltaY, incrementalDeltaY); + // No need to do all this work if we're not going to move anyway + if (incrementalDeltaY != 0) { + trackMotionScroll(deltaY, incrementalDeltaY); + } // Check to see if we have bumped into the scroll limit View motionView = this.getChildAt(mMotionPosition - mFirstPosition); @@ -2041,7 +2066,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (mSelector != null) { Drawable d = mSelector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { - ((TransitionDrawable)d).resetTransition(); + ((TransitionDrawable) d).resetTransition(); } } postDelayed(new Runnable() { @@ -2065,15 +2090,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mTouchMode = TOUCH_MODE_REST; break; case TOUCH_MODE_SCROLL: - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - final int initialVelocity = (int) velocityTracker.getYVelocity(); - if (Math.abs(initialVelocity) > mMinimumVelocity && (getChildCount() > 0)) { - if (mFlingRunnable == null) { - mFlingRunnable = new FlingRunnable(); + final int childCount = getChildCount(); + if (childCount > 0) { + if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top && + mFirstPosition + childCount < mItemCount && + getChildAt(childCount - 1).getBottom() <= + getHeight() - mListPadding.bottom) { + mTouchMode = TOUCH_MODE_REST; + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); + } else { + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + final int initialVelocity = (int) velocityTracker.getYVelocity(); + + if (Math.abs(initialVelocity) > mMinimumVelocity) { + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } + reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); + mFlingRunnable.start(-initialVelocity); + } } - reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); - mFlingRunnable.start(-initialVelocity); } else { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); @@ -2166,6 +2203,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te clearScrollingCache(); } mLastY = Integer.MIN_VALUE; + if (mTouchMode == TOUCH_MODE_FLING) { + return true; + } break; } @@ -2407,7 +2447,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) { hideSelector(); offsetChildrenTopAndBottom(incrementalDeltaY); - invalidate(); + if (!awakenScrollBars()) { + invalidate(); + } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; } else { final int firstPosition = mFirstPosition; @@ -2490,6 +2532,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mBlockLayoutRequests = false; invokeOnItemScrollListener(); + awakenScrollBars(); } } @@ -2908,10 +2951,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te okToSend = false; break; case KeyEvent.KEYCODE_BACK: - if (mFiltered && mPopup != null && mPopup.isShowing() && - event.getAction() == KeyEvent.ACTION_DOWN) { - handled = true; - mTextFilter.setText(""); + if (mFiltered && mPopup != null && mPopup.isShowing()) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + getKeyDispatcherState().startTracking(event, this); + handled = true; + } else if (event.getAction() == KeyEvent.ACTION_UP + && event.isTracking() && !event.isCanceled()) { + handled = true; + mTextFilter.setText(""); + } } okToSend = false; break; diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index f92eb99..d25530b 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -64,10 +64,10 @@ public abstract class AbsSeekBar extends ProgressBar { TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.SeekBar, defStyle, 0); Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); - setThumb(thumb); + setThumb(thumb); // will guess mThumbOffset if thumb != null... + // ...but allow layout to override this int thumbOffset = - a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, 0); - setThumbOffset(thumbOffset); + a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset()); a.recycle(); a = context.obtainStyledAttributes(attrs, @@ -77,13 +77,21 @@ public abstract class AbsSeekBar extends ProgressBar { } /** - * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar + * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar. + * <p> + * If the thumb is a valid drawable (i.e. not null), half its width will be + * used as the new thumb offset (@see #setThumbOffset(int)). * * @param thumb Drawable representing the thumb */ public void setThumb(Drawable thumb) { if (thumb != null) { thumb.setCallback(this); + + // Assuming the thumb drawable is symmetric, set the thumb offset + // such that the thumb will hang halfway off either edge of the + // progress bar. + mThumbOffset = (int)thumb.getIntrinsicWidth() / 2; } mThumb = thumb; invalidate(); @@ -293,11 +301,16 @@ public abstract class AbsSeekBar extends ProgressBar { trackTouchEvent(event); onStopTrackingTouch(); setPressed(false); + // ProgressBar doesn't know to repaint the thumb drawable + // in its inactive state when the touch stops (because the + // value has not apparently changed) + invalidate(); break; case MotionEvent.ACTION_CANCEL: onStopTrackingTouch(); setPressed(false); + invalidate(); // see above explanation break; } return true; diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index 7d2fcbc..fe6d91a 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -163,7 +163,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { /** * View to show if there are no items to show. */ - View mEmptyView; + private View mEmptyView; /** * The number of items in the current adapter. diff --git a/core/java/android/widget/AlphabetIndexer.java b/core/java/android/widget/AlphabetIndexer.java index f50676a..59b2c2a 100644 --- a/core/java/android/widget/AlphabetIndexer.java +++ b/core/java/android/widget/AlphabetIndexer.java @@ -28,7 +28,7 @@ import android.util.SparseIntArray; * invalidates the cache if changes occur in the cursor. * <p/> * Your adapter is responsible for updating the cursor by calling {@link #setCursor} if the - * cursor changes. {@link #getPositionForSection} method does the binary search for the starting + * cursor changes. {@link #getPositionForSection} method does the binary search for the starting * index of a given section (alphabet). */ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { @@ -37,33 +37,33 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { * Cursor that is used by the adapter of the list view. */ protected Cursor mDataCursor; - + /** * The index of the cursor column that this list is sorted on. */ protected int mColumnIndex; - + /** * The string of characters that make up the indexing sections. */ protected CharSequence mAlphabet; - + /** * Cached length of the alphabet array. */ private int mAlphabetLength; - + /** * This contains a cache of the computed indices so far. It will get reset whenever * the dataset changes or the cursor changes. */ private SparseIntArray mAlphaMap; - + /** * Use a collator to compare strings in a localized manner. */ private java.text.Collator mCollator; - + /** * The section array converted from the alphabet string. */ @@ -72,9 +72,9 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { /** * Constructs the indexer. * @param cursor the cursor containing the data set - * @param sortedColumnIndex the column number in the cursor that is sorted + * @param sortedColumnIndex the column number in the cursor that is sorted * alphabetically - * @param alphabet string containing the alphabet, with space as the first character. + * @param alphabet string containing the alphabet, with space as the first character. * For example, use the string " ABCDEFGHIJKLMNOPQRSTUVWXYZ" for English indexing. * The characters must be uppercase and be sorted in ascii/unicode order. Basically * characters in the alphabet will show up as preview letters. @@ -104,7 +104,7 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { public Object[] getSections() { return mAlphabetArray; } - + /** * Sets a new cursor as the data set and resets the cache of indices. * @param cursor the new cursor to use as the data set @@ -124,9 +124,16 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { * Default implementation compares the first character of word with letter. */ protected int compare(String word, String letter) { - return mCollator.compare(word.substring(0, 1), letter); + final String firstLetter; + if (word.length() == 0) { + firstLetter = " "; + } else { + firstLetter = word.substring(0, 1); + } + + return mCollator.compare(firstLetter, letter); } - + /** * Performs a binary search or cache lookup to find the first row that * matches a given section's starting letter. @@ -143,7 +150,7 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { if (cursor == null || mAlphabet == null) { return 0; } - + // Check bounds if (sectionIndex <= 0) { return 0; @@ -164,7 +171,7 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { int key = letter; // Check map if (Integer.MIN_VALUE != (pos = alphaMap.get(key, Integer.MIN_VALUE))) { - // Is it approximate? Using negative value to indicate that it's + // Is it approximate? Using negative value to indicate that it's // an approximation and positive value when it is the accurate // position. if (pos < 0) { @@ -204,7 +211,7 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { } int diff = compare(curName, targetLetter); if (diff != 0) { - // Commenting out approximation code because it doesn't work for certain + // TODO: Commenting out approximation code because it doesn't work for certain // lists with custom comparators // Enter approximation in hash if a better solution doesn't exist // String startingLetter = Character.toString(getFirstLetter(curName)); @@ -259,9 +266,9 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer { return i; } } - return 0; // Don't recognize the letter - falls under zero'th section + return 0; // Don't recognize the letter - falls under zero'th section } - + /* * @hide */ diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index 6579660..a09f23c 100755 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -26,13 +26,10 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; -import java.io.File; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -149,15 +146,6 @@ public class AppSecurityPermissions implements View.OnClickListener { } } - public PackageParser.Package getPackageInfo(Uri packageURI) { - final String archiveFilePath = packageURI.getPath(); - PackageParser packageParser = new PackageParser(archiveFilePath); - File sourceFile = new File(archiveFilePath); - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0); - } - private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) { String sharedPkgList[] = mPm.getPackagesForUid(sharedUid); if(sharedPkgList == null || (sharedPkgList.length == 0)) { diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 02d77d1..75d0f31 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -82,6 +82,8 @@ import com.android.internal.R; * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight + * @attr ref android.R.styleable#AutoCompleteTextView_dropDownVerticalOffset + * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHorizontalOffset */ public class AutoCompleteTextView extends EditText implements Filter.FilterListener { static final boolean DEBUG = false; @@ -194,7 +196,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe setFocusable(true); addTextChangedListener(new MyWatcher()); - + mPassThroughClickListener = new PassThroughClickListener(); super.setOnClickListener(mPassThroughClickListener); } @@ -321,8 +323,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * @return the background drawable * * @attr ref android.R.styleable#PopupWindow_popupBackground - * - * @hide Pending API council approval */ public Drawable getDropDownBackground() { return mPopup.getBackground(); @@ -334,8 +334,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * @param d the drawable to set as the background * * @attr ref android.R.styleable#PopupWindow_popupBackground - * - * @hide Pending API council approval */ public void setDropDownBackgroundDrawable(Drawable d) { mPopup.setBackgroundDrawable(d); @@ -347,47 +345,15 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * @param id the id of the drawable to set as the background * * @attr ref android.R.styleable#PopupWindow_popupBackground - * - * @hide Pending API council approval */ public void setDropDownBackgroundResource(int id) { mPopup.setBackgroundDrawable(getResources().getDrawable(id)); } - - /** - * <p>Sets the animation style of the auto-complete drop-down list.</p> - * - * <p>If the drop-down is showing, calling this method will take effect only - * the next time the drop-down is shown.</p> - * - * @param animationStyle animation style to use when the drop-down appears - * and disappears. Set to -1 for the default animation, 0 for no - * animation, or a resource identifier for an explicit animation. - * - * @hide Pending API council approval - */ - public void setDropDownAnimationStyle(int animationStyle) { - mPopup.setAnimationStyle(animationStyle); - } - - /** - * <p>Returns the animation style that is used when the drop-down list appears and disappears - * </p> - * - * @return the animation style that is used when the drop-down list appears and disappears - * - * @hide Pending API council approval - */ - public int getDropDownAnimationStyle() { - return mPopup.getAnimationStyle(); - } /** * <p>Sets the vertical offset used for the auto-complete drop-down list.</p> * * @param offset the vertical offset - * - * @hide Pending API council approval */ public void setDropDownVerticalOffset(int offset) { mDropDownVerticalOffset = offset; @@ -397,8 +363,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * <p>Gets the vertical offset used for the auto-complete drop-down list.</p> * * @return the vertical offset - * - * @hide Pending API council approval */ public int getDropDownVerticalOffset() { return mDropDownVerticalOffset; @@ -408,8 +372,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * <p>Sets the horizontal offset used for the auto-complete drop-down list.</p> * * @param offset the horizontal offset - * - * @hide Pending API council approval */ public void setDropDownHorizontalOffset(int offset) { mDropDownHorizontalOffset = offset; @@ -419,13 +381,39 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe * <p>Gets the horizontal offset used for the auto-complete drop-down list.</p> * * @return the horizontal offset - * - * @hide Pending API council approval */ public int getDropDownHorizontalOffset() { return mDropDownHorizontalOffset; } + /** + * <p>Sets the animation style of the auto-complete drop-down list.</p> + * + * <p>If the drop-down is showing, calling this method will take effect only + * the next time the drop-down is shown.</p> + * + * @param animationStyle animation style to use when the drop-down appears + * and disappears. Set to -1 for the default animation, 0 for no + * animation, or a resource identifier for an explicit animation. + * + * @hide Pending API council approval + */ + public void setDropDownAnimationStyle(int animationStyle) { + mPopup.setAnimationStyle(animationStyle); + } + + /** + * <p>Returns the animation style that is used when the drop-down list appears and disappears + * </p> + * + * @return the animation style that is used when the drop-down list appears and disappears + * + * @hide Pending API council approval + */ + public int getDropDownAnimationStyle() { + return mPopup.getAnimationStyle(); + } + /** * @return Whether the drop-down is visible as long as there is {@link #enoughToFilter()} * @@ -617,12 +605,20 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { - if (isPopupShowing()) { + if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing() + && !mDropDownAlwaysVisible) { // special case for the back key, we do not even try to send it // to the drop down list but instead, consume it immediately - if (keyCode == KeyEvent.KEYCODE_BACK && !mDropDownAlwaysVisible) { - dismissDropDown(); + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + getKeyDispatcherState().startTracking(event, this); return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + getKeyDispatcherState().handleUpEvent(event); + if (event.isTracking() && !event.isCanceled()) { + dismissDropDown(); + return true; + } } } return super.onKeyPreIme(keyCode, event); @@ -1161,7 +1157,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe heightSpec = mDropDownHeight; } - mPopup.setOutsideTouchable(mForceIgnoreOutsideTouch ? false : !mDropDownAlwaysVisible); + mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, widthSpec, heightSpec); @@ -1191,8 +1187,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe // use outside touchable to dismiss drop down when touching outside of it, so // only set this if the dropdown is not always visible - mPopup.setOutsideTouchable(mForceIgnoreOutsideTouch ? false : !mDropDownAlwaysVisible); - mPopup.setTouchInterceptor(new PopupTouchIntercepter()); + mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible); + mPopup.setTouchInterceptor(new PopupTouchInterceptor()); mPopup.showAsDropDown(getDropDownAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset); mDropDownList.setSelection(ListView.INVALID_POSITION); @@ -1333,7 +1329,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe final int maxHeight = mPopup.getMaxAvailableHeight( getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations); - if (mDropDownAlwaysVisible) { + if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) { // getMaxAvailableHeight() subtracts the padding, so we put it back, // to get the available height for the whole window int padding = 0; @@ -1416,9 +1412,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } } - private class PopupTouchIntercepter implements OnTouchListener { + private class PopupTouchInterceptor implements OnTouchListener { public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (event.getAction() == MotionEvent.ACTION_DOWN && + mPopup != null && mPopup.isShowing()) { mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); showDropDown(); } @@ -1595,7 +1592,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe */ CharSequence fixText(CharSequence invalidText); } - + /** * Allows us a private hook into the on click event without preventing users from setting * their own click listener. @@ -1611,5 +1608,5 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe if (mWrapped != null) mWrapped.onClick(v); } } - + } diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index 5360621..6abb2ae 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -33,6 +33,7 @@ import android.view.ContextMenu; import android.view.SoundEffectConstants; import android.view.View; import android.view.ContextMenu.ContextMenuInfo; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ExpandableListConnector.PositionMetadata; /** @@ -916,7 +917,14 @@ public class ExpandableListView extends ListView { @Override ContextMenuInfo createContextMenuInfo(View view, int flatListPosition, long id) { - PositionMetadata pm = mConnector.getUnflattenedPos(flatListPosition); + // Adjust for and handle for header views + final int adjustedPosition = flatListPosition - getHeaderViewsCount(); + if (adjustedPosition < 0) { + // Return normal info for header view context menus + return new AdapterContextMenuInfo(view, flatListPosition, id); + } + + PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition); ExpandableListPosition pos = pm.position; pm.recycle(); diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 2da777a..67c0def 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -55,7 +55,7 @@ class FastScroller { private int mThumbY; private RectF mOverlayPos; - private int mOverlaySize = 104; + private int mOverlaySize; private AbsListView mList; private boolean mScrollCompleted; @@ -119,10 +119,10 @@ class FastScroller { private void useThumbDrawable(Context context, Drawable drawable) { mThumbDrawable = drawable; - mThumbW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 64, context.getResources().getDisplayMetrics()); - mThumbH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 52, context.getResources().getDisplayMetrics()); + mThumbW = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.fastscroll_thumb_width); + mThumbH = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.fastscroll_thumb_height); mChangedBounds = true; } @@ -138,7 +138,9 @@ class FastScroller { mScrollCompleted = true; getSectionsFromIndexer(); - + + mOverlaySize = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.fastscroll_overlay_size); mOverlayPos = new RectF(); mScrollFade = new ScrollFade(); mPaint = new Paint(); diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index e7b303a..f34823c 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Config; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; @@ -36,8 +35,6 @@ import android.view.ViewGroup; import android.view.SoundEffectConstants; import android.view.ContextMenu.ContextMenuInfo; import android.view.animation.Transformation; -import android.widget.AbsSpinner; -import android.widget.Scroller; /** * A view that shows items in a center-locked, horizontally scrolling list. @@ -59,7 +56,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList private static final String TAG = "Gallery"; - private static final boolean localLOGV = Config.LOGV; + private static final boolean localLOGV = false; /** * Duration in milliseconds from the start of a scroll during which we're @@ -514,6 +511,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList // We haven't been callbacking during the fling, so do it now super.selectionChanged(); } + invalidate(); } @Override @@ -534,12 +532,9 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList int galleryCenter = getCenterOfGallery(); - if (selView != null) { - - // Common case where the current selected position is correct - if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) { - return; - } + // Common case where the current selected position is correct + if (selView.getLeft() <= galleryCenter && selView.getRight() >= galleryCenter) { + return; } // TODO better search @@ -627,7 +622,6 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList View sel = makeAndAddView(mSelectedPosition, 0, 0, true); // Put the selected child in the center - Gallery.LayoutParams lp = (Gallery.LayoutParams) sel.getLayoutParams(); int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2); sel.offsetLeftAndRight(selectedOffset); @@ -733,9 +727,6 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList child = mRecycler.get(position); if (child != null) { // Can reuse an existing view - Gallery.LayoutParams lp = (Gallery.LayoutParams) - child.getLayoutParams(); - int childLeft = child.getLeft(); // Remember left and right edges of where views have been placed @@ -798,7 +789,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList int childRight; // Position vertically based on gravity setting - int childTop = calculateTop(child, lp, true); + int childTop = calculateTop(child, true); int childBottom = childTop + child.getMeasuredHeight(); int width = child.getMeasuredWidth(); @@ -817,11 +808,9 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList * Figure out vertical placement based on mGravity * * @param child Child to place - * @param lp LayoutParams for this view (just so we don't keep looking them - * up) * @return Where the top of the child should be */ - private int calculateTop(View child, Gallery.LayoutParams lp, boolean duringLayout) { + private int calculateTop(View child, boolean duringLayout) { int myHeight = duringLayout ? mMeasuredHeight : getHeight(); int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight(); diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 9ec8347..ffe9908 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -220,6 +220,8 @@ public class GridView extends AbsListView { selectedView = temp; } + // mReferenceView will change with each call to makeRow() + // do not cache in a local variable outside of this loop nextTop = mReferenceView.getBottom() + mVerticalSpacing; pos += mNumColumns; @@ -233,7 +235,8 @@ public class GridView extends AbsListView { final int horizontalSpacing = mHorizontalSpacing; int last; - int nextLeft = mListPadding.left + ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0); + int nextLeft = mListPadding.left + + ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0); if (!mStackFromBottom) { last = Math.min(startPos + mNumColumns, mItemCount); @@ -252,16 +255,14 @@ public class GridView extends AbsListView { final boolean inClick = touchModeDrawsInPressedState(); final int selectedPosition = mSelectedPosition; - mReferenceView = null; - + View child = null; for (int pos = startPos; pos < last; pos++) { // is this the selected item? boolean selected = pos == selectedPosition; // does the list view have focus or contain focus final int where = flow ? -1 : pos - startPos; - final View child = makeAndAddView(pos, y, flow, nextLeft, selected, where); - mReferenceView = child; + child = makeAndAddView(pos, y, flow, nextLeft, selected, where); nextLeft += columnWidth; if (pos < last - 1) { @@ -273,6 +274,8 @@ public class GridView extends AbsListView { } } + mReferenceView = child; + if (selectedView != null) { mReferenceViewInSelectedRow = mReferenceView; } @@ -465,6 +468,11 @@ public class GridView extends AbsListView { mFirstPosition = motionRowStart; final View referenceView = mReferenceView; + // We didn't have anything to layout, bail out + if (referenceView == null) { + return null; + } + final int verticalSpacing = mVerticalSpacing; View above; @@ -1148,9 +1156,12 @@ public class GridView extends AbsListView { if (sel != null) { positionSelector(sel); mSelectedTop = sel.getTop(); + } else if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) { + View child = getChildAt(mMotionPosition - mFirstPosition); + if (child != null) positionSelector(child); } else { - mSelectedTop = 0; - mSelectorRect.setEmpty(); + mSelectedTop = 0; + mSelectorRect.setEmpty(); } mLayoutMode = LAYOUT_NORMAL; @@ -1231,8 +1242,12 @@ public class GridView extends AbsListView { private void setupChild(View child, int position, int y, boolean flow, int childrenLeft, boolean selected, boolean recycled, int where) { boolean isSelected = selected && shouldShowSelector(); - final boolean updateChildSelected = isSelected != child.isSelected(); + final int mode = mTouchMode; + final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL && + mMotionPosition == position; + final boolean updateChildPressed = isPressed != child.isPressed(); + boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested(); // Respect layout params that are already in the view. Otherwise make @@ -1257,6 +1272,10 @@ public class GridView extends AbsListView { } } + if (updateChildPressed) { + child.setPressed(isPressed); + } + if (needToMeasure) { int childHeightSpec = ViewGroup.getChildMeasureSpec( MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height); @@ -1329,8 +1348,23 @@ public class GridView extends AbsListView { */ @Override void setSelectionInt(int position) { + int previousSelectedPosition = mNextSelectedPosition; + setNextSelectedPositionInt(position); layoutChildren(); + + final int next = mStackFromBottom ? mItemCount - 1 - mNextSelectedPosition : + mNextSelectedPosition; + final int previous = mStackFromBottom ? mItemCount - 1 + - previousSelectedPosition : previousSelectedPosition; + + final int nextRow = next / mNumColumns; + final int previousRow = previous / mNumColumns; + + if (nextRow != previousRow) { + awakenScrollBars(); + } + } @Override @@ -1460,6 +1494,7 @@ public class GridView extends AbsListView { if (nextPage >= 0) { setSelectionInt(nextPage); invokeOnItemScrollListener(); + awakenScrollBars(); return true; } @@ -1486,6 +1521,10 @@ public class GridView extends AbsListView { invokeOnItemScrollListener(); moved = true; } + + if (moved) { + awakenScrollBars(); + } return moved; } @@ -1552,6 +1591,10 @@ public class GridView extends AbsListView { invokeOnItemScrollListener(); } + if (moved) { + awakenScrollBars(); + } + return moved; } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 46e514c..52f56a7 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -829,6 +829,7 @@ public class HorizontalScrollView extends FrameLayout { long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; if (duration > ANIMATED_SCROLL_GAP) { mScroller.startScroll(mScrollX, mScrollY, dx, dy); + awakenScrollBars(mScroller.getDuration()); invalidate(); } else { if (!mScroller.isFinished()) { @@ -1172,6 +1173,7 @@ public class HorizontalScrollView extends FrameLayout { mScrollViewMovedFocus = false; } + awakenScrollBars(mScroller.getDuration()); invalidate(); } } diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index a9822f8..6cc794b 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -59,7 +59,7 @@ public class LinearLayout extends ViewGroup { * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned * with whether the children of this layout are baseline aligned. */ - private int mBaselineAlignedChildIndex = 0; + private int mBaselineAlignedChildIndex = -1; /** * The additional offset to the child's baseline. diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 515b581..7c8151e 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1328,19 +1328,23 @@ public class ListView extends AbsListView { // Make sure we are 1) Too low, and 2) Either there are more rows below the // last row or the last row is scrolled off the bottom of the drawable area - if (topOffset > 0 && (lastPosition < mItemCount - 1 || lastBottom > end)) { - if (lastPosition == mItemCount - 1 ) { - // Don't pull the bottom too far up - topOffset = Math.min(topOffset, lastBottom - end); - } - // Move everything up - offsetChildrenTopAndBottom(-topOffset); - if (lastPosition < mItemCount - 1) { - // Fill the gap that was opened below the last position with more rows, if - // possible - fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight); - // Close up the remaining gap - adjustViewsUpOrDown(); + if (topOffset > 0) { + if (lastPosition < mItemCount - 1 || lastBottom > end) { + if (lastPosition == mItemCount - 1) { + // Don't pull the bottom too far up + topOffset = Math.min(topOffset, lastBottom - end); + } + // Move everything up + offsetChildrenTopAndBottom(-topOffset); + if (lastPosition < mItemCount - 1) { + // Fill the gap that was opened below the last position with more rows, if + // possible + fillDown(lastPosition + 1, lastChild.getBottom() + mDividerHeight); + // Close up the remaining gap + adjustViewsUpOrDown(); + } + } else if (lastPosition == mItemCount - 1) { + adjustViewsUpOrDown(); } } } @@ -1428,7 +1432,8 @@ public class ListView extends AbsListView { throw new IllegalStateException("The content of the adapter has changed but " + "ListView did not receive a notification. Make sure the content of " + "your adapter is not modified from a background thread, but only " - + "from the UI thread."); + + "from the UI thread. [in ListView(" + getId() + ", " + getClass() + + ") with Adapter(" + mAdapter.getClass() + ")]"); } setSelectedPositionInt(mNextSelectedPosition); @@ -1537,37 +1542,42 @@ public class ListView extends AbsListView { recycleBin.scrapActiveViews(); if (sel != null) { - // the current selected item should get focus if items - // are focusable - if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) { - final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild && - focusLayoutRestoreView.requestFocus()) || sel.requestFocus(); - if (!focusWasTaken) { - // selected item didn't take focus, fine, but still want - // to make sure something else outside of the selected view - // has focus - final View focused = getFocusedChild(); - if (focused != null) { - focused.clearFocus(); - } - positionSelector(sel); - } else { - sel.setSelected(false); - mSelectorRect.setEmpty(); - } - } else { - positionSelector(sel); - } - mSelectedTop = sel.getTop(); + // the current selected item should get focus if items + // are focusable + if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) { + final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild && + focusLayoutRestoreView.requestFocus()) || sel.requestFocus(); + if (!focusWasTaken) { + // selected item didn't take focus, fine, but still want + // to make sure something else outside of the selected view + // has focus + final View focused = getFocusedChild(); + if (focused != null) { + focused.clearFocus(); + } + positionSelector(sel); + } else { + sel.setSelected(false); + mSelectorRect.setEmpty(); + } + } else { + positionSelector(sel); + } + mSelectedTop = sel.getTop(); } else { - mSelectedTop = 0; - mSelectorRect.setEmpty(); + if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) { + View child = getChildAt(mMotionPosition - mFirstPosition); + if (child != null) positionSelector(child); + } else { + mSelectedTop = 0; + mSelectorRect.setEmpty(); + } - // even if there is not selected position, we may need to restore - // focus (i.e. something focusable in touch mode) - if (hasFocus() && focusLayoutRestoreView != null) { - focusLayoutRestoreView.requestFocus(); - } + // even if there is not selected position, we may need to restore + // focus (i.e. something focusable in touch mode) + if (hasFocus() && focusLayoutRestoreView != null) { + focusLayoutRestoreView.requestFocus(); + } } // tell focus view we are done mucking with it, if it is still in @@ -1681,6 +1691,10 @@ public class ListView extends AbsListView { boolean selected, boolean recycled) { final boolean isSelected = selected && shouldShowSelector(); final boolean updateChildSelected = isSelected != child.isSelected(); + final int mode = mTouchMode; + final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL && + mMotionPosition == position; + final boolean updateChildPressed = isPressed != child.isPressed(); final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested(); // Respect layout params that are already in the view. Otherwise make some up... @@ -1706,6 +1720,10 @@ public class ListView extends AbsListView { child.setSelected(isSelected); } + if (updateChildPressed) { + child.setPressed(isPressed); + } + if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) { if (child instanceof Checkable) { ((Checkable) child).setChecked(mCheckStates.get(position)); @@ -1800,13 +1818,29 @@ public class ListView extends AbsListView { /** * Makes the item at the supplied position selected. - * + * * @param position the position of the item to select */ @Override void setSelectionInt(int position) { setNextSelectedPositionInt(position); + boolean awakeScrollbars = false; + + final int selectedPosition = mSelectedPosition; + + if (selectedPosition >= 0) { + if (position == selectedPosition - 1) { + awakeScrollbars = true; + } else if (position == selectedPosition + 1) { + awakeScrollbars = true; + } + } + layoutChildren(); + + if (awakeScrollbars) { + awakenScrollBars(); + } } /** @@ -2066,7 +2100,9 @@ public class ListView extends AbsListView { setSelectionInt(position); invokeOnItemScrollListener(); - invalidate(); + if (!awakenScrollBars()) { + invalidate(); + } return true; } @@ -2107,7 +2143,8 @@ public class ListView extends AbsListView { } } - if (moved) { + if (moved && !awakenScrollBars()) { + awakenScrollBars(); invalidate(); } @@ -2252,7 +2289,9 @@ public class ListView extends AbsListView { positionSelector(selectedView); mSelectedTop = selectedView.getTop(); } - invalidate(); + if (!awakenScrollBars()) { + invalidate(); + } invokeOnItemScrollListener(); return true; } @@ -3264,12 +3303,13 @@ public class ListView extends AbsListView { if (mChoiceMode == CHOICE_MODE_MULTIPLE) { mCheckStates.put(position, value); } else { - // Clear the old value: if something was selected and value == false - // then it is unselected - mCheckStates.clear(); - // If value == true, select the appropriate position + // Clear all values if we're checking something, or unchecking the currently + // selected item + if (value || isItemChecked(position)) { + mCheckStates.clear(); + } // this may end up selecting the value we just cleared but this way - // we don't have to first to a get(position) + // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on if (value) { mCheckStates.put(position, true); } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index 0c9d980..446a992 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -250,6 +250,29 @@ public class MediaController extends FrameLayout { } /** + * Disable pause or seek buttons if the stream cannot be paused or seeked. + * This requires the control interface to be a MediaPlayerControlExt + */ + private void disableUnsupportedButtons() { + try { + if (mPauseButton != null && !mPlayer.canPause()) { + mPauseButton.setEnabled(false); + } + if (mRewButton != null && !mPlayer.canSeekBackward()) { + mRewButton.setEnabled(false); + } + if (mFfwdButton != null && !mPlayer.canSeekForward()) { + mFfwdButton.setEnabled(false); + } + } catch (IncompatibleClassChangeError ex) { + // We were given an old version of the interface, that doesn't have + // the canPause/canSeekXYZ methods. This is OK, it just means we + // assume the media can be paused and seeked, and so we don't disable + // the buttons. + } + } + + /** * Show the controller on screen. It will go away * automatically after 'timeout' milliseconds of inactivity. * @param timeout The timeout in milliseconds. Use 0 to show @@ -259,6 +282,10 @@ public class MediaController extends FrameLayout { if (!mShowing && mAnchor != null) { setProgress(); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + } + disableUnsupportedButtons(); int [] anchorpos = new int[2]; mAnchor.getLocationOnScreen(anchorpos); @@ -392,6 +419,9 @@ public class MediaController extends FrameLayout { keyCode == KeyEvent.KEYCODE_SPACE)) { doPauseResume(); show(sDefaultTimeout); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP) { if (mPlayer.isPlaying()) { @@ -421,17 +451,13 @@ public class MediaController extends FrameLayout { }; private void updatePausePlay() { - if (mRoot == null) - return; - - ImageButton button = (ImageButton) mRoot.findViewById(com.android.internal.R.id.pause); - if (button == null) + if (mRoot == null || mPauseButton == null) return; if (mPlayer.isPlaying()) { - button.setImageResource(com.android.internal.R.drawable.ic_media_pause); + mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause); } else { - button.setImageResource(com.android.internal.R.drawable.ic_media_play); + mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play); } } @@ -516,7 +542,7 @@ public class MediaController extends FrameLayout { if (mProgress != null) { mProgress.setEnabled(enabled); } - + disableUnsupportedButtons(); super.setEnabled(enabled); } @@ -579,5 +605,8 @@ public class MediaController extends FrameLayout { void seekTo(int pos); boolean isPlaying(); int getBufferPercentage(); - }; + boolean canPause(); + boolean canSeekBackward(); + boolean canSeekForward(); + } } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 90fbb77..d86b674 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -817,6 +817,7 @@ public class PopupWindow { * @param p the layout parameters of the popup's content view */ private void invokePopup(WindowManager.LayoutParams p) { + p.packageName = mContext.getPackageName(); mWindowManager.addView(mPopupView, p); } @@ -1322,8 +1323,16 @@ public class PopupWindow { @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - dismiss(); - return true; + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + getKeyDispatcherState().startTracking(event, this); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP + && event.isTracking() && !event.isCanceled()) { + dismiss(); + return true; + } + return super.dispatchKeyEvent(event); } else { return super.dispatchKeyEvent(event); } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index b179a13..2f28d9f 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -651,6 +651,7 @@ public class ProgressBar extends View { if (mProgress > max) { mProgress = max; + refreshProgress(R.id.progress, mProgress, false); } } } diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java new file mode 100644 index 0000000..8019f14 --- /dev/null +++ b/core/java/android/widget/QuickContactBadge.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2009 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 android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.QuickContact; +import android.provider.ContactsContract.Intents; +import android.provider.ContactsContract.PhoneLookup; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import com.android.internal.R; + +/** + * Widget used to show an image with the standard QuickContact badge + * and on-click behavior. + */ +public class QuickContactBadge extends ImageView implements OnClickListener { + + private Uri mContactUri; + private String mContactEmail; + private String mContactPhone; + private int mMode; + private QueryHandler mQueryHandler; + private Drawable mBadgeBackground; + private Drawable mNoBadgeBackground; + + protected String[] mExcludeMimes = null; + + static final private int TOKEN_EMAIL_LOOKUP = 0; + static final private int TOKEN_PHONE_LOOKUP = 1; + static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2; + static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3; + + static final String[] EMAIL_LOOKUP_PROJECTION = new String[] { + RawContacts.CONTACT_ID, + Contacts.LOOKUP_KEY, + }; + static int EMAIL_ID_COLUMN_INDEX = 0; + static int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1; + + static final String[] PHONE_LOOKUP_PROJECTION = new String[] { + PhoneLookup._ID, + PhoneLookup.LOOKUP_KEY, + }; + static int PHONE_ID_COLUMN_INDEX = 0; + static int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1; + + + + public QuickContactBadge(Context context) { + this(context, null); + } + + public QuickContactBadge(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public QuickContactBadge(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = + context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.QuickContactBadge, defStyle, 0); + + mMode = a.getInt(com.android.internal.R.styleable.QuickContactBadge_quickContactWindowSize, + QuickContact.MODE_MEDIUM); + + a.recycle(); + + init(); + + mBadgeBackground = getBackground(); + } + + private void init() { + mQueryHandler = new QueryHandler(mContext.getContentResolver()); + setOnClickListener(this); + } + + /** + * Set the QuickContact window mode. Options are {@link QuickContact#MODE_SMALL}, + * {@link QuickContact#MODE_MEDIUM}, {@link QuickContact#MODE_LARGE}. + * @param size + */ + public void setMode(int size) { + mMode = size; + } + + /** + * Assign the contact uri that this QuickContactBadge should be associated + * with. Note that this is only used for displaying the QuickContact window and + * won't bind the contact's photo for you. + * + * @param contactUri Either a {@link Contacts#CONTENT_URI} or + * {@link Contacts#CONTENT_LOOKUP_URI} style URI. + */ + public void assignContactUri(Uri contactUri) { + mContactUri = contactUri; + mContactEmail = null; + mContactPhone = null; + onContactUriChanged(); + } + + private void onContactUriChanged() { + if (mContactUri == null && mContactEmail == null && mContactPhone == null) { + if (mNoBadgeBackground == null) { + mNoBadgeBackground = getResources().getDrawable(R.drawable.quickcontact_nobadge); + } + setBackgroundDrawable(mNoBadgeBackground); + } else { + setBackgroundDrawable(mBadgeBackground); + } + } + + /** + * Assign a contact based on an email address. This should only be used when + * the contact's URI is not available, as an extra query will have to be + * performed to lookup the URI based on the email. + * + * @param emailAddress The email address of the contact. + * @param lazyLookup If this is true, the lookup query will not be performed + * until this view is clicked. + */ + public void assignContactFromEmail(String emailAddress, boolean lazyLookup) { + mContactEmail = emailAddress; + if (!lazyLookup) { + mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null, + Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)), + EMAIL_LOOKUP_PROJECTION, null, null, null); + } else { + mContactUri = null; + onContactUriChanged(); + } + } + + /** + * Assign a contact based on a phone number. This should only be used when + * the contact's URI is not available, as an extra query will have to be + * performed to lookup the URI based on the phone number. + * + * @param phoneNumber The phone number of the contact. + * @param lazyLookup If this is true, the lookup query will not be performed + * until this view is clicked. + */ + public void assignContactFromPhone(String phoneNumber, boolean lazyLookup) { + mContactPhone = phoneNumber; + if (!lazyLookup) { + mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null, + Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone), + PHONE_LOOKUP_PROJECTION, null, null, null); + } else { + mContactUri = null; + onContactUriChanged(); + } + } + + public void onClick(View v) { + if (mContactUri != null) { + final ContentResolver resolver = getContext().getContentResolver(); + final Uri lookupUri = Contacts.getLookupUri(resolver, mContactUri); + trigger(lookupUri); + } else if (mContactEmail != null) { + mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail, + Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)), + EMAIL_LOOKUP_PROJECTION, null, null, null); + } else if (mContactPhone != null) { + mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, mContactPhone, + Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone), + PHONE_LOOKUP_PROJECTION, null, null, null); + } else { + // If a contact hasn't been assigned, don't react to click. + return; + } + } + + /** + * Set a list of specific MIME-types to exclude and not display. For + * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE} + * profile icon. + */ + public void setExcludeMimes(String[] excludeMimes) { + mExcludeMimes = excludeMimes; + } + + private void trigger(Uri lookupUri) { + QuickContact.showQuickContact(getContext(), this, lookupUri, mMode, mExcludeMimes); + } + + private class QueryHandler extends AsyncQueryHandler { + + public QueryHandler(ContentResolver cr) { + super(cr); + } + + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + Uri lookupUri = null; + Uri createUri = null; + boolean trigger = false; + + try { + switch(token) { + case TOKEN_PHONE_LOOKUP_AND_TRIGGER: + trigger = true; + createUri = Uri.fromParts("tel", (String)cookie, null); + + case TOKEN_PHONE_LOOKUP: { + if (cursor != null && cursor.moveToFirst()) { + long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX); + String lookupKey = cursor.getString(PHONE_LOOKUP_STRING_COLUMN_INDEX); + lookupUri = Contacts.getLookupUri(contactId, lookupKey); + } + + break; + } + case TOKEN_EMAIL_LOOKUP_AND_TRIGGER: + trigger = true; + createUri = Uri.fromParts("mailto", (String)cookie, null); + + case TOKEN_EMAIL_LOOKUP: { + if (cursor != null && cursor.moveToFirst()) { + long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX); + String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX); + lookupUri = Contacts.getLookupUri(contactId, lookupKey); + } + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + mContactUri = lookupUri; + onContactUriChanged(); + + if (trigger && lookupUri != null) { + // Found contact, so trigger track + trigger(lookupUri); + } else if (createUri != null) { + // Prompt user to add this person to contacts + final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri); + getContext().startActivity(intent); + } + } + } +} diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2dac652..6771711 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -17,8 +17,9 @@ package android.widget; import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.graphics.PorterDuff; @@ -137,9 +138,12 @@ public class RemoteViews implements Parcelable, Filter { public void onClick(View v) { try { // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? - pendingIntent.send(); - } catch (CanceledException e) { - throw new ActionException(e.toString()); + v.getContext().startIntentSender( + pendingIntent.getIntentSender(), null, + Intent.FLAG_ACTIVITY_NEW_TASK, + Intent.FLAG_ACTIVITY_NEW_TASK, 0); + } catch (IntentSender.SendIntentException e) { + android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e); } } }; diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 703cd8e..24d97a5 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -743,7 +743,7 @@ public class ScrollView extends FrameLayout { final int maxJump = getMaxScrollAmount(); - if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump)) { + if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) { nextFocused.getDrawingRect(mTempRect); offsetDescendantRectToMyCoords(nextFocused, mTempRect); int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); @@ -792,19 +792,19 @@ public class ScrollView extends FrameLayout { * screen. */ private boolean isOffScreen(View descendant) { - return !isWithinDeltaOfScreen(descendant, 0); + return !isWithinDeltaOfScreen(descendant, 0, getHeight()); } /** * @return whether the descendant of this scroll view is within delta * pixels of being on the screen. */ - private boolean isWithinDeltaOfScreen(View descendant, int delta) { + private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) { descendant.getDrawingRect(mTempRect); offsetDescendantRectToMyCoords(descendant, mTempRect); return (mTempRect.bottom + delta) >= getScrollY() - && (mTempRect.top - delta) <= (getScrollY() + getHeight()); + && (mTempRect.top - delta) <= (getScrollY() + height); } /** @@ -832,6 +832,7 @@ public class ScrollView extends FrameLayout { long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; if (duration > ANIMATED_SCROLL_GAP) { mScroller.startScroll(mScrollX, mScrollY, dx, dy); + awakenScrollBars(mScroller.getDuration()); invalidate(); } else { if (!mScroller.isFinished()) { @@ -1124,9 +1125,10 @@ public class ScrollView extends FrameLayout { if (null == currentFocused || this == currentFocused) return; - final int maxJump = mBottom - mTop; - - if (isWithinDeltaOfScreen(currentFocused, maxJump)) { + // If the currently-focused view was visible on the screen when the + // screen was at the old height, then scroll the screen to make that + // view visible with the new screen height. + if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) { currentFocused.getDrawingRect(mTempRect); offsetDescendantRectToMyCoords(currentFocused, mTempRect); int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); @@ -1174,6 +1176,7 @@ public class ScrollView extends FrameLayout { mScrollViewMovedFocus = false; } + awakenScrollBars(mScroller.getDuration()); invalidate(); } } diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java index 381641f..11dab02 100644 --- a/core/java/android/widget/Scroller.java +++ b/core/java/android/widget/Scroller.java @@ -133,6 +133,17 @@ public class Scroller { } /** + * @hide + * Returns the current velocity. + * + * @return The original velocity less the deceleration. Result may be + * negative. + */ + public float getCurrVelocity() { + return mVelocity - mDeceleration * timePassed() / 2000.0f; + } + + /** * Returns the start X offset in the scroll. * * @return The start X offset as an absolute distance from the origin. diff --git a/core/java/android/widget/SimpleCursorTreeAdapter.java b/core/java/android/widget/SimpleCursorTreeAdapter.java index c456f56..a1c65f0 100644 --- a/core/java/android/widget/SimpleCursorTreeAdapter.java +++ b/core/java/android/widget/SimpleCursorTreeAdapter.java @@ -26,9 +26,16 @@ import android.view.View; * defined in an XML file. You can specify which columns you want, which views * you want to display the columns, and the XML file that defines the appearance * of these views. Separate XML files for child and groups are possible. - * TextViews bind the values to their text property (see - * {@link TextView#setText(CharSequence)}). ImageViews bind the values to their - * image's Uri property (see {@link ImageView#setImageURI(android.net.Uri)}). + * + * Binding occurs in two phases. First, if a + * {@link android.widget.SimpleCursorTreeAdapter.ViewBinder} is available, + * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)} + * is invoked. If the returned value is true, binding has occurred. If the + * returned value is false and the view to bind is a TextView, + * {@link #setViewText(TextView, String)} is invoked. If the returned value + * is false and the view to bind is an ImageView, + * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate + * binding can be found, an {@link IllegalStateException} is thrown. */ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter { /** The indices of columns that contain data to display for a group. */ @@ -48,6 +55,11 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter private int[] mChildTo; /** + * View binder, if supplied + */ + private ViewBinder mViewBinder; + + /** * Constructor. * * @param context The context where the {@link ExpandableListView} @@ -193,21 +205,53 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter initFromColumns(childCursor, childFromNames, mChildFrom); } + /** + * Returns the {@link ViewBinder} used to bind data to views. + * + * @return a ViewBinder or null if the binder does not exist + * + * @see #setViewBinder(android.widget.SimpleCursorTreeAdapter.ViewBinder) + */ + public ViewBinder getViewBinder() { + return mViewBinder; + } + + /** + * Sets the binder used to bind data to views. + * + * @param viewBinder the binder used to bind data to views, can be null to + * remove the existing binder + * + * @see #getViewBinder() + */ + public void setViewBinder(ViewBinder viewBinder) { + mViewBinder = viewBinder; + } + private void bindView(View view, Context context, Cursor cursor, int[] from, int[] to) { + final ViewBinder binder = mViewBinder; + for (int i = 0; i < to.length; i++) { View v = view.findViewById(to[i]); if (v != null) { - String text = cursor.getString(from[i]); - if (text == null) { - text = ""; + boolean bound = false; + if (binder != null) { + bound = binder.setViewValue(v, cursor, from[i]); } - if (v instanceof TextView) { - ((TextView) v).setText(text); - } else if (v instanceof ImageView) { - setViewImage((ImageView) v, text); - } else { - throw new IllegalStateException("SimpleCursorAdapter can bind values only to" + - " TextView and ImageView!"); + + if (!bound) { + String text = cursor.getString(from[i]); + if (text == null) { + text = ""; + } + if (v instanceof TextView) { + setViewText((TextView) v, text); + } else if (v instanceof ImageView) { + setViewImage((ImageView) v, text); + } else { + throw new IllegalStateException("SimpleCursorTreeAdapter can bind values" + + " only to TextView and ImageView!"); + } } } } @@ -238,4 +282,48 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter v.setImageURI(Uri.parse(value)); } } + + /** + * Called by bindView() to set the text for a TextView but only if + * there is no existing ViewBinder or if the existing ViewBinder cannot + * handle binding to an TextView. + * + * Intended to be overridden by Adapters that need to filter strings + * retrieved from the database. + * + * @param v TextView to receive text + * @param text the text to be set for the TextView + */ + public void setViewText(TextView v, String text) { + v.setText(text); + } + + /** + * This class can be used by external clients of SimpleCursorTreeAdapter + * to bind values from the Cursor to views. + * + * You should use this class to bind values from the Cursor to views + * that are not directly supported by SimpleCursorTreeAdapter or to + * change the way binding occurs for views supported by + * SimpleCursorTreeAdapter. + * + * @see SimpleCursorTreeAdapter#setViewImage(ImageView, String) + * @see SimpleCursorTreeAdapter#setViewText(TextView, String) + */ + public static interface ViewBinder { + /** + * Binds the Cursor column defined by the specified index to the specified view. + * + * When binding is handled by this ViewBinder, this method must return true. + * If this method returns false, SimpleCursorTreeAdapter will attempts to handle + * the binding on its own. + * + * @param view the view to bind the data to + * @param cursor the cursor to get the data from + * @param columnIndex the column at which the data can be found in the cursor + * + * @return true if the data was bound to the view, false otherwise + */ + boolean setViewValue(View view, Cursor cursor, int columnIndex); + } } diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java index 103d44d..31920e7 100644 --- a/core/java/android/widget/TabHost.java +++ b/core/java/android/widget/TabHost.java @@ -20,6 +20,7 @@ import android.app.LocalActivityManager; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -28,11 +29,12 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; -import com.android.internal.R; import java.util.ArrayList; import java.util.List; +import com.android.internal.R; + /** * Container for a tabbed window view. This object holds two children: a set of tab labels that the * user clicks to select a specific tab, and a FrameLayout object that displays the contents of that @@ -68,7 +70,7 @@ public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchMode initTabHost(); } - private final void initTabHost() { + private void initTabHost() { setFocusableInTouchMode(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); @@ -101,8 +103,8 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); throw new RuntimeException( "Your TabHost must have a TabWidget whose id attribute is 'android.R.id.tabs'"); } - - // KeyListener to attach to all tabs. Detects non-navigation keys + + // KeyListener to attach to all tabs. Detects non-navigation keys // and relays them to the tab content. mTabKeyListener = new OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { @@ -114,14 +116,14 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_ENTER: return false; - + } mTabContent.requestFocus(View.FOCUS_FORWARD); return mTabContent.dispatchKeyEvent(event); } - + }; - + mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() { public void onTabSelectionChanged(int tabIndex, boolean clicked) { setCurrentTab(tabIndex); @@ -134,7 +136,8 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); mTabContent = (FrameLayout) findViewById(com.android.internal.R.id.tabcontent); if (mTabContent == null) { throw new RuntimeException( - "Your TabHost must have a FrameLayout whose id attribute is 'android.R.id.tabcontent'"); + "Your TabHost must have a FrameLayout whose id attribute is " + + "'android.R.id.tabcontent'"); } } @@ -176,7 +179,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); if (!isInTouchMode) { // leaving touch mode.. if nothing has focus, let's give it to // the indicator of the current tab - if (!mCurrentView.hasFocus() || mCurrentView.isFocused()) { + if (mCurrentView != null && (!mCurrentView.hasFocus() || mCurrentView.isFocused())) { mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus(); } } @@ -283,7 +286,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); playSoundEffect(SoundEffectConstants.NAVIGATION_UP); return true; } - return handled; + return handled; } @@ -312,7 +315,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); // Call the tab widget's focusCurrentTab(), instead of just // selecting the tab. mTabWidget.focusCurrentTab(mCurrentTab); - + // tab content mCurrentView = spec.mContentStrategy.getContentView(); @@ -367,7 +370,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); public interface TabContentFactory { /** * Callback to make the tab contents - * + * * @param tag * Which tab was selected. * @return The view to display the contents of the selected tab. @@ -496,8 +499,9 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); } public View createIndicatorView() { + final Context context = getContext(); LayoutInflater inflater = - (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View tabIndicator = inflater.inflate(R.layout.tab_indicator, mTabWidget, // tab widget is the parent false); // no inflate params @@ -505,6 +509,12 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); final TextView tv = (TextView) tabIndicator.findViewById(R.id.title); tv.setText(mLabel); + if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { + // Donut apps get old color scheme + tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4); + tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4)); + } + return tabIndicator; } } @@ -523,8 +533,9 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); } public View createIndicatorView() { + final Context context = getContext(); LayoutInflater inflater = - (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View tabIndicator = inflater.inflate(R.layout.tab_indicator, mTabWidget, // tab widget is the parent false); // no inflate params @@ -535,6 +546,12 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon); iconView.setImageDrawable(mIcon); + if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { + // Donut apps get old color scheme + tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4); + tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4)); + } + return tabIndicator; } } @@ -637,7 +654,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); } } mLaunchedView = wd; - + // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get // focus if none of their children have it. They need focus to be able to // display menu items. diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 47f5c6c..2ba6268 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -16,11 +16,15 @@ package android.widget; +import com.android.internal.R; + import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -30,7 +34,7 @@ import android.view.View.OnFocusChangeListener; /** - * + * * Displays a list of tab labels representing each page in the parent's tab * collection. The container object for this widget is * {@link android.widget.TabHost TabHost}. When the user selects a tab, this @@ -64,25 +68,54 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { super(context, attrs); initTabWidget(); - TypedArray a = + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget, defStyle, 0); a.recycle(); } - + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mStripMoved = true; super.onSizeChanged(w, h, oldw, oldh); } + @Override + protected int getChildDrawingOrder(int childCount, int i) { + // Always draw the selected tab last, so that drop shadows are drawn + // in the correct z-order. + if (i == childCount - 1) { + return mSelectedTab; + } else if (i >= mSelectedTab) { + return i + 1; + } else { + return i; + } + } + private void initTabWidget() { setOrientation(LinearLayout.HORIZONTAL); - mBottomLeftStrip = mContext.getResources().getDrawable( - com.android.internal.R.drawable.tab_bottom_left); - mBottomRightStrip = mContext.getResources().getDrawable( - com.android.internal.R.drawable.tab_bottom_right); + mGroupFlags |= FLAG_USE_CHILD_DRAWING_ORDER; + + final Context context = mContext; + final Resources resources = context.getResources(); + + if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { + // Donut apps get old color scheme + mBottomLeftStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_left_v4); + mBottomRightStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_right_v4); + } else { + // Use modern color scheme for Eclair and beyond + mBottomLeftStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_left); + mBottomRightStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_right); + } + + // Deal with focus, as we don't want the focus to go by default // to a tab other than the current tab setFocusable(true); @@ -156,7 +189,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { } super.childDrawableStateChanged(child); } - + @Override public void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); @@ -169,17 +202,17 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { } View selectedChild = getChildTabViewAt(mSelectedTab); - + mBottomLeftStrip.setState(selectedChild.getDrawableState()); mBottomRightStrip.setState(selectedChild.getDrawableState()); - + if (mStripMoved) { Rect selBounds = new Rect(); // Bounds of the selected tab indicator selBounds.left = selectedChild.getLeft(); selBounds.right = selectedChild.getRight(); final int myHeight = getHeight(); mBottomLeftStrip.setBounds( - Math.min(0, selBounds.left + Math.min(0, selBounds.left - mBottomLeftStrip.getIntrinsicWidth()), myHeight - mBottomLeftStrip.getIntrinsicHeight(), selBounds.left, @@ -187,12 +220,12 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { mBottomRightStrip.setBounds( selBounds.right, myHeight - mBottomRightStrip.getIntrinsicHeight(), - Math.max(getWidth(), + Math.max(getWidth(), selBounds.right + mBottomRightStrip.getIntrinsicWidth()), myHeight); mStripMoved = false; } - + mBottomLeftStrip.draw(canvas); mBottomRightStrip.draw(canvas); } @@ -202,26 +235,26 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { * This method is used to bring a tab to the front of the Widget, * and is used to post to the rest of the UI that a different tab * has been brought to the foreground. - * - * Note, this is separate from the traditional "focus" that is - * employed from the view logic. - * - * For instance, if we have a list in a tabbed view, a user may be - * navigating up and down the list, moving the UI focus (orange - * highlighting) through the list items. The cursor movement does - * not effect the "selected" tab though, because what is being - * scrolled through is all on the same tab. The selected tab only - * changes when we navigate between tabs (moving from the list view + * + * Note, this is separate from the traditional "focus" that is + * employed from the view logic. + * + * For instance, if we have a list in a tabbed view, a user may be + * navigating up and down the list, moving the UI focus (orange + * highlighting) through the list items. The cursor movement does + * not effect the "selected" tab though, because what is being + * scrolled through is all on the same tab. The selected tab only + * changes when we navigate between tabs (moving from the list view * to the next tabbed view, in this example). - * + * * To move both the focus AND the selected tab at once, please use - * {@link #setCurrentTab}. Normally, the view logic takes care of - * adjusting the focus, so unless you're circumventing the UI, + * {@link #setCurrentTab}. Normally, the view logic takes care of + * adjusting the focus, so unless you're circumventing the UI, * you'll probably just focus your interest here. - * + * * @param index The tab that you want to indicate as the selected * tab (tab brought to the front of the widget) - * + * * @see #focusCurrentTab */ public void setCurrentTab(int index) { @@ -234,19 +267,19 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { getChildTabViewAt(mSelectedTab).setSelected(true); mStripMoved = true; } - + /** * Sets the current tab and focuses the UI on it. - * This method makes sure that the focused tab matches the selected - * tab, normally at {@link #setCurrentTab}. Normally this would not - * be an issue if we go through the UI, since the UI is responsible - * for calling TabWidget.onFocusChanged(), but in the case where we - * are selecting the tab programmatically, we'll need to make sure + * This method makes sure that the focused tab matches the selected + * tab, normally at {@link #setCurrentTab}. Normally this would not + * be an issue if we go through the UI, since the UI is responsible + * for calling TabWidget.onFocusChanged(), but in the case where we + * are selecting the tab programmatically, we'll need to make sure * focus keeps up. - * - * @param index The tab that you want focused (highlighted in orange) + * + * @param index The tab that you want focused (highlighted in orange) * and selected (tab brought to the front of the widget) - * + * * @see #setCurrentTab */ public void focusCurrentTab(int index) { @@ -254,18 +287,18 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { // set the tab setCurrentTab(index); - + // change the focus if applicable. if (oldTab != index) { getChildTabViewAt(index).requestFocus(); } } - + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); int count = getTabCount(); - + for (int i = 0; i < count; i++) { View child = getChildTabViewAt(i); child.setEnabled(enabled); @@ -318,7 +351,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { getChildTabViewAt(mSelectedTab).requestFocus(); return; } - + if (hasFocus) { int i = 0; int numTabs = getTabCount(); @@ -354,7 +387,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { /** * Informs the TabHost which tab was selected. It also indicates * if the tab was clicked/pressed or just focused into. - * + * * @param tabIndex index of the tab that was selected * @param clicked whether the selection changed due to a touch/click * or due to focus entering the tab through navigation. Pass true diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 88730aac..f55ca3f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -327,11 +327,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mText = ""; mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.density = getResources().getDisplayMetrics().density; + mTextPaint.setCompatibilityScaling( + getResources().getCompatibilityInfo().applicationScale); + // If we get the paint from the skin, we should set it to left, since // the layout always wants it to be left. // mTextPaint.setTextAlign(Paint.Align.LEFT); mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mHighlightPaint.setCompatibilityScaling( + getResources().getCompatibilityInfo().applicationScale); mMovement = getDefaultMovementMethod(); mTransformation = null; @@ -731,7 +737,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } else if (digits != null) { mInput = DigitsKeyListener.getInstance(digits.toString()); - mInputType = inputType; + // If no input type was specified, we will default to generic + // text, since we can't tell the IME about the set of digits + // that was selected. + mInputType = inputType != EditorInfo.TYPE_NULL + ? inputType : EditorInfo.TYPE_CLASS_TEXT; } else if (inputType != EditorInfo.TYPE_NULL) { setInputType(inputType, true); singleLine = (inputType&(EditorInfo.TYPE_MASK_CLASS @@ -2869,26 +2879,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_inputType */ public void setInputType(int type) { + final boolean wasPassword = isPasswordInputType(mInputType); + final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType); setInputType(type, false); - final int variation = type&(EditorInfo.TYPE_MASK_CLASS - |EditorInfo.TYPE_MASK_VARIATION); - final boolean isPassword = variation - == (EditorInfo.TYPE_CLASS_TEXT - |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + final boolean isPassword = isPasswordInputType(type); + final boolean isVisiblePassword = isVisiblePasswordInputType(type); boolean forceUpdate = false; if (isPassword) { setTransformationMethod(PasswordTransformationMethod.getInstance()); setTypefaceByIndex(MONOSPACE, 0); - } else if (mTransformation == PasswordTransformationMethod.getInstance()) { - // We need to clean up if we were previously in password mode. - if (variation != (EditorInfo.TYPE_CLASS_TEXT - |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) { - setTypefaceByIndex(-1, -1); - } - forceUpdate = true; - } else if (variation == (EditorInfo.TYPE_CLASS_TEXT - |EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) { + } else if (isVisiblePassword) { + if (mTransformation == PasswordTransformationMethod.getInstance()) { + forceUpdate = true; + } setTypefaceByIndex(MONOSPACE, 0); + } else if (wasPassword || wasVisiblePassword) { + // not in password mode, clean up typeface and transformation + setTypefaceByIndex(-1, -1); + if (mTransformation == PasswordTransformationMethod.getInstance()) { + forceUpdate = true; + } } boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS @@ -2908,6 +2918,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } + private boolean isPasswordInputType(int inputType) { + final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS + | EditorInfo.TYPE_MASK_VARIATION); + return variation + == (EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + } + + private boolean isVisiblePasswordInputType(int inputType) { + final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS + | EditorInfo.TYPE_MASK_VARIATION); + return variation + == (EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + } + /** * Directly change the content type integer of the text view, without * modifying any other state. @@ -3403,7 +3429,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mPopup != null) { TextView tv = (TextView) mPopup.getContentView(); chooseSize(mPopup, mError, tv); - mPopup.update(this, getErrorX(), getErrorY(), -1, -1); + mPopup.update(this, getErrorX(), getErrorY(), + mPopup.getWidth(), mPopup.getHeight()); } restartMarqueeIfNeeded(); @@ -3937,8 +3964,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mHighlightPath = new Path(); if (selStart == selEnd) { - if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) - < BLINK) { + if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) { if (mHighlightPathBogus) { mHighlightPath.reset(); mLayout.getCursorPath(selStart, mHighlightPath, mText); @@ -5317,21 +5343,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * will happen at measure). */ makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, - mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); - - // In a fixed-height view, so use our new text layout. - if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && - mLayoutParams.height != LayoutParams.FILL_PARENT) { - invalidate(); - return; - } + mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), + false); - // Dynamic height, but height has stayed the same, - // so use our new text layout. - if (mLayout.getHeight() == oldht && - (mHintLayout == null || mHintLayout.getHeight() == oldht)) { - invalidate(); - return; + if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { + // In a fixed-height view, so use our new text layout. + if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && + mLayoutParams.height != LayoutParams.FILL_PARENT) { + invalidate(); + return; + } + + // Dynamic height, but height has stayed the same, + // so use our new text layout. + if (mLayout.getHeight() == oldht && + (mHintLayout == null || mHintLayout.getHeight() == oldht)) { + invalidate(); + return; + } } // We lose: the height has changed and we have a dynamic height. @@ -5553,6 +5582,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (duration > ANIMATED_SCROLL_GAP) { mScroller.startScroll(mScrollX, mScrollY, dx, dy); + awakenScrollBars(mScroller.getDuration()); invalidate(); } else { if (!mScroller.isFinished()) { @@ -6916,6 +6946,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + boolean hasLetter = false; + for (int i = start; i < end; i++) { + if (Character.isLetter(mTransformed.charAt(i))) { + hasLetter = true; + break; + } + } + if (!hasLetter) { + return null; + } + if (start == end) { return null; } diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java index 9a72980..eab6f2d 100644 --- a/core/java/android/widget/TwoLineListItem.java +++ b/core/java/android/widget/TwoLineListItem.java @@ -36,8 +36,7 @@ import android.widget.RelativeLayout; * that can be displayed when a TwoLineListItem has focus. Android supplies a * {@link android.R.layout#two_line_list_item standard layout resource for TwoLineListView} * (which does not include a selected item icon), but you can design your own custom XML - * layout for this object as shown in the Phone Application. - * (packages/apps/Phone/res/layout/dialer_list_item.xml) + * layout for this object. * * @attr ref android.R.styleable#TwoLineListItem_mode */ diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 20dd8a6..8142a82 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.res.Resources; import android.media.AudioManager; import android.media.MediaPlayer; +import android.media.Metadata; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.net.Uri; @@ -34,7 +35,7 @@ import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; -import android.widget.MediaController.MediaPlayerControl; +import android.widget.MediaController.*; import java.io.IOException; @@ -51,11 +52,26 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { private Uri mUri; private int mDuration; + // all possible internal states + private static final int STATE_ERROR = -1; + private static final int STATE_IDLE = 0; + private static final int STATE_PREPARING = 1; + private static final int STATE_PREPARED = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; + private static final int STATE_PLAYBACK_COMPLETED = 5; + + // mCurrentState is a VideoView object's current state. + // mTargetState is the state that a method caller intends to reach. + // For instance, regardless the VideoView object's current state, + // calling pause() intends to bring the object to a target state + // of STATE_PAUSED. + private int mCurrentState = STATE_IDLE; + private int mTargetState = STATE_IDLE; + // All the stuff we need for playing and showing a video private SurfaceHolder mSurfaceHolder = null; private MediaPlayer mMediaPlayer = null; - private boolean mIsPrepared; - private boolean mIsPlaybackCompleted; private int mVideoWidth; private int mVideoHeight; private int mSurfaceWidth; @@ -65,8 +81,10 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { private MediaPlayer.OnPreparedListener mOnPreparedListener; private int mCurrentBufferPercentage; private OnErrorListener mOnErrorListener; - private boolean mStartWhenPrepared; - private int mSeekWhenPrepared; + private int mSeekWhenPrepared; // recording the seek position while preparing + private boolean mCanPause; + private boolean mCanSeekBack; + private boolean mCanSeekForward; public VideoView(Context context) { super(context); @@ -80,7 +98,6 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { public VideoView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - initVideoView(); } @@ -143,6 +160,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { setFocusable(true); setFocusableInTouchMode(true); requestFocus(); + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; } public void setVideoPath(String path) { @@ -151,7 +170,6 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { public void setVideoURI(Uri uri) { mUri = uri; - mStartWhenPrepared = false; mSeekWhenPrepared = 0; openVideo(); requestLayout(); @@ -163,6 +181,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; } } @@ -176,18 +196,14 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { Intent i = new Intent("com.android.music.musicservicecommand"); i.putExtra("command", "pause"); mContext.sendBroadcast(i); - - if (mMediaPlayer != null) { - mMediaPlayer.reset(); - mMediaPlayer.release(); - mMediaPlayer = null; - } + + // we shouldn't clear the target state, because somebody might have + // called start() previously + release(false); try { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); - mIsPrepared = false; - Log.v(TAG, "reset duration to -1 in openVideo"); mDuration = -1; mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); @@ -198,12 +214,21 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mMediaPlayer.setScreenOnWhilePlaying(true); mMediaPlayer.prepareAsync(); + // we don't set the target state here either, but preserve the + // target state that was there before. + mCurrentState = STATE_PREPARING; attachMediaController(); } catch (IOException ex) { Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); return; } catch (IllegalArgumentException ex) { Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); return; } } @@ -222,7 +247,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { View anchorView = this.getParent() instanceof View ? (View)this.getParent() : this; mMediaController.setAnchorView(anchorView); - mMediaController.setEnabled(mIsPrepared); + mMediaController.setEnabled(isInPlaybackState()); } } @@ -239,8 +264,23 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { - // briefly show the mediacontroller - mIsPrepared = true; + mCurrentState = STATE_PREPARED; + + // Get the capabilities of the player for this stream + Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, + MediaPlayer.BYPASS_METADATA_FILTER); + + if (data != null) { + mCanPause = !data.has(Metadata.PAUSE_AVAILABLE) + || data.getBoolean(Metadata.PAUSE_AVAILABLE); + mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE) + || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE); + mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE) + || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE); + } else { + mCanPause = mCanSeekForward = mCanSeekForward = true; + } + if (mOnPreparedListener != null) { mOnPreparedListener.onPrepared(mMediaPlayer); } @@ -249,6 +289,11 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { } mVideoWidth = mp.getVideoWidth(); mVideoHeight = mp.getVideoHeight(); + + int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call + if (seekToPosition != 0) { + seekTo(seekToPosition); + } if (mVideoWidth != 0 && mVideoHeight != 0) { //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); getHolder().setFixedSize(mVideoWidth, mVideoHeight); @@ -256,18 +301,13 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { // We didn't actually change the size (it was already at the size // we need), so we won't get a "surface changed" callback, so // start the video here instead of in the callback. - if (mSeekWhenPrepared != 0) { - mMediaPlayer.seekTo(mSeekWhenPrepared); - mSeekWhenPrepared = 0; - } - if (mStartWhenPrepared) { + if (mTargetState == STATE_PLAYING) { start(); - mStartWhenPrepared = false; if (mMediaController != null) { mMediaController.show(); } } else if (!isPlaying() && - (mSeekWhenPrepared != 0 || getCurrentPosition() > 0)) { + (seekToPosition != 0 || getCurrentPosition() > 0)) { if (mMediaController != null) { // Show the media controls when we're paused into a video and make 'em stick. mMediaController.show(0); @@ -277,13 +317,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { } else { // We don't know the video size yet, but should start anyway. // The video size might be reported to us later. - if (mSeekWhenPrepared != 0) { - mMediaPlayer.seekTo(mSeekWhenPrepared); - mSeekWhenPrepared = 0; - } - if (mStartWhenPrepared) { + if (mTargetState == STATE_PLAYING) { start(); - mStartWhenPrepared = false; } } } @@ -292,7 +327,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() { public void onCompletion(MediaPlayer mp) { - mIsPlaybackCompleted = true; + mCurrentState = STATE_PLAYBACK_COMPLETED; + mTargetState = STATE_PLAYBACK_COMPLETED; if (mMediaController != null) { mMediaController.hide(); } @@ -306,6 +342,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { new MediaPlayer.OnErrorListener() { public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { Log.d(TAG, "Error: " + framework_err + "," + impl_err); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; if (mMediaController != null) { mMediaController.hide(); } @@ -402,14 +440,13 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { { mSurfaceWidth = w; mSurfaceHeight = h; - if (mMediaPlayer != null && mIsPrepared && mVideoWidth == w && mVideoHeight == h) { + boolean isValidState = (mTargetState == STATE_PLAYING); + boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); + if (mMediaPlayer != null && isValidState && hasValidSize) { if (mSeekWhenPrepared != 0) { - mMediaPlayer.seekTo(mSeekWhenPrepared); - mSeekWhenPrepared = 0; + seekTo(mSeekWhenPrepared); } - if (!mIsPlaybackCompleted) { - start(); - } + start(); if (mMediaController != null) { mMediaController.show(); } @@ -427,17 +464,28 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { // after we return from this we can't use the surface any more mSurfaceHolder = null; if (mMediaController != null) mMediaController.hide(); - if (mMediaPlayer != null) { - mMediaPlayer.reset(); - mMediaPlayer.release(); - mMediaPlayer = null; - } + release(true); } }; + /* + * release the media player in any state + */ + private void release(boolean cleartargetstate) { + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + if (cleartargetstate) { + mTargetState = STATE_IDLE; + } + } + } + @Override public boolean onTouchEvent(MotionEvent ev) { - if (mIsPrepared && mMediaPlayer != null && mMediaController != null) { + if (isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); } return false; @@ -445,7 +493,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { @Override public boolean onTrackballEvent(MotionEvent ev) { - if (mIsPrepared && mMediaPlayer != null && mMediaController != null) { + if (isInPlaybackState() && mMediaController != null) { toggleMediaControlsVisiblity(); } return false; @@ -454,15 +502,13 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - if (mIsPrepared && - keyCode != KeyEvent.KEYCODE_BACK && - keyCode != KeyEvent.KEYCODE_VOLUME_UP && - keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && - keyCode != KeyEvent.KEYCODE_MENU && - keyCode != KeyEvent.KEYCODE_CALL && - keyCode != KeyEvent.KEYCODE_ENDCALL && - mMediaPlayer != null && - mMediaController != null) { + boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_MENU && + keyCode != KeyEvent.KEYCODE_CALL && + keyCode != KeyEvent.KEYCODE_ENDCALL; + if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { if (mMediaPlayer.isPlaying()) { @@ -494,26 +540,26 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { } public void start() { - mIsPlaybackCompleted = false; - if (mMediaPlayer != null && mIsPrepared) { - mMediaPlayer.start(); - mStartWhenPrepared = false; - } else { - mStartWhenPrepared = true; + if (isInPlaybackState()) { + mMediaPlayer.start(); + mCurrentState = STATE_PLAYING; } + mTargetState = STATE_PLAYING; } public void pause() { - if (mMediaPlayer != null && mIsPrepared) { + if (isInPlaybackState()) { if (mMediaPlayer.isPlaying()) { mMediaPlayer.pause(); + mCurrentState = STATE_PAUSED; } } - mStartWhenPrepared = false; + mTargetState = STATE_PAUSED; } + // cache duration as mDuration for faster access public int getDuration() { - if (mMediaPlayer != null && mIsPrepared) { + if (isInPlaybackState()) { if (mDuration > 0) { return mDuration; } @@ -525,25 +571,23 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { } public int getCurrentPosition() { - if (mMediaPlayer != null && mIsPrepared) { + if (isInPlaybackState()) { return mMediaPlayer.getCurrentPosition(); } return 0; } public void seekTo(int msec) { - if (mMediaPlayer != null && mIsPrepared) { + if (isInPlaybackState()) { mMediaPlayer.seekTo(msec); + mSeekWhenPrepared = 0; } else { mSeekWhenPrepared = msec; } } public boolean isPlaying() { - if (mMediaPlayer != null && mIsPrepared) { - return mMediaPlayer.isPlaying(); - } - return false; + return isInPlaybackState() && mMediaPlayer.isPlaying(); } public int getBufferPercentage() { @@ -552,4 +596,23 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { } return 0; } + + private boolean isInPlaybackState() { + return (mMediaPlayer != null && + mCurrentState != STATE_ERROR && + mCurrentState != STATE_IDLE && + mCurrentState != STATE_PREPARING); + } + + public boolean canPause() { + return mCanPause; + } + + public boolean canSeekBackward() { + return mCanSeekBack; + } + + public boolean canSeekForward() { + return mCanSeekForward; + } } diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index a41e2e3..e55fbb8 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -249,7 +249,7 @@ public class ZoomButtonsController implements View.OnTouchListener { lp.height = LayoutParams.WRAP_CONTENT; lp.width = LayoutParams.FILL_PARENT; lp.type = LayoutParams.TYPE_APPLICATION_PANEL; - lp.format = PixelFormat.TRANSPARENT; + lp.format = PixelFormat.TRANSLUCENT; lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons; mContainerLayoutParams = lp; @@ -476,7 +476,21 @@ public class ZoomButtonsController implements View.OnTouchListener { if (isInterestingKey(keyCode)) { if (keyCode == KeyEvent.KEYCODE_BACK) { - setVisible(false); + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + if (mOwnerView != null) { + KeyEvent.DispatcherState ds = mOwnerView.getKeyDispatcherState(); + if (ds != null) { + ds.startTracking(event, this); + } + } + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP + && event.isTracking() && !event.isCanceled()) { + setVisible(false); + return true; + } + } else { dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); } |