summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/widget')
-rw-r--r--core/java/android/widget/AbsListView.java85
-rw-r--r--core/java/android/widget/AbsSeekBar.java21
-rw-r--r--core/java/android/widget/AdapterView.java2
-rw-r--r--core/java/android/widget/AlphabetIndexer.java41
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java12
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java105
-rw-r--r--core/java/android/widget/ExpandableListView.java10
-rw-r--r--core/java/android/widget/FastScroller.java14
-rw-r--r--core/java/android/widget/Gallery.java25
-rw-r--r--core/java/android/widget/GridView.java59
-rw-r--r--core/java/android/widget/HorizontalScrollView.java2
-rw-r--r--core/java/android/widget/LinearLayout.java2
-rw-r--r--core/java/android/widget/ListView.java144
-rw-r--r--core/java/android/widget/MediaController.java47
-rw-r--r--core/java/android/widget/PopupWindow.java13
-rw-r--r--core/java/android/widget/ProgressBar.java1
-rw-r--r--core/java/android/widget/QuickContactBadge.java272
-rw-r--r--core/java/android/widget/RemoteViews.java12
-rw-r--r--core/java/android/widget/ScrollView.java17
-rw-r--r--core/java/android/widget/Scroller.java11
-rw-r--r--core/java/android/widget/SimpleCursorTreeAdapter.java114
-rw-r--r--core/java/android/widget/TabHost.java47
-rw-r--r--core/java/android/widget/TabWidget.java117
-rw-r--r--core/java/android/widget/TextView.java105
-rw-r--r--core/java/android/widget/TwoLineListItem.java3
-rw-r--r--core/java/android/widget/VideoView.java201
-rw-r--r--core/java/android/widget/ZoomButtonsController.java18
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);
}