diff options
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/util/ArrayMap.java | 123 | ||||
| -rw-r--r-- | core/java/android/util/ArraySet.java | 599 | ||||
| -rw-r--r-- | core/java/android/view/WindowManagerPolicy.java | 20 | ||||
| -rw-r--r-- | core/java/android/widget/FastScroller.java | 145 | ||||
| -rw-r--r-- | core/java/android/widget/FrameLayout.java | 19 | ||||
| -rw-r--r-- | core/java/android/widget/HorizontalScrollView.java | 93 | ||||
| -rw-r--r-- | core/java/android/widget/ListView.java | 111 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/PointerLocationView.java | 5 |
8 files changed, 935 insertions, 180 deletions
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java index bb0a6e1..4bd9b37 100644 --- a/core/java/android/util/ArrayMap.java +++ b/core/java/android/util/ArrayMap.java @@ -206,6 +206,7 @@ public final class ArrayMap<K, V> implements Map<K, V> { } mSize = 0; } + /** * Create a new ArrayMap with the mappings from the given ArrayMap. */ @@ -216,7 +217,6 @@ public final class ArrayMap<K, V> implements Map<K, V> { } } - /** * Make the array map empty. All storage is released. */ @@ -236,8 +236,8 @@ public final class ArrayMap<K, V> implements Map<K, V> { */ public void ensureCapacity(int minimumCapacity) { if (mHashes.length < minimumCapacity) { - int[] ohashes = mHashes; - Object[] oarray = mArray; + final int[] ohashes = mHashes; + final Object[] oarray = mArray; allocArrays(minimumCapacity); if (mSize > 0) { System.arraycopy(ohashes, 0, mHashes, 0, mSize); @@ -493,6 +493,63 @@ public final class ArrayMap<K, V> implements Map<K, V> { return mSize; } + /** + * {@inheritDoc} + * + * <p>This implementation returns false if the object is not a map, or + * if the maps have different sizes. Otherwise, for each key in this map, + * values of both maps are compared. If the values for any key are not + * equal, the method returns false, otherwise it returns true. + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Map) { + Map<?, ?> map = (Map<?, ?>) object; + if (size() != map.size()) { + return false; + } + + try { + for (int i=0; i<mSize; i++) { + K key = keyAt(i); + V mine = valueAt(i); + Object theirs = map.get(key); + if (mine == null) { + if (theirs != null || !map.containsKey(key)) { + return false; + } + } else if (!mine.equals(theirs)) { + return false; + } + } + } catch (NullPointerException ignored) { + return false; + } catch (ClassCastException ignored) { + return false; + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int[] hashes = mHashes; + final Object[] array = mArray; + int result = 0; + for (int i = 0, v = 1, s = mSize; i < s; i++, v+=2) { + Object value = array[v]; + result += hashes[i] ^ (value == null ? 0 : value.hashCode()); + } + return result; + } + // ------------------------------------------------------------------------ // Interop with traditional Java containers. Not as efficient as using // specialized collection APIs. @@ -632,64 +689,4 @@ public final class ArrayMap<K, V> implements Map<K, V> { public Collection<V> values() { return getCollection().getValues(); } - - /** - * {@inheritDoc} - * - * <p>This implementation returns false if the object is not a map, or - * if the maps have different sizes. Otherwise, for each key in this map, - * values of both maps are compared. If the values for any key are not - * equal, the method returns false, otherwise it returns true. - */ - @Override - public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object instanceof Map) { - Map<?, ?> map = (Map<?, ?>) object; - if (size() != map.size()) { - return false; - } - - try { - for (int i=0; i<mSize; i++) { - K key = keyAt(i); - V mine = valueAt(i); - Object theirs = map.get(key); - if (mine == null) { - if (theirs != null || !map.containsKey(key)) { - return false; - } - } else if (!mine.equals(theirs)) { - return false; - } - } - } catch (NullPointerException ignored) { - return false; - } catch (ClassCastException ignored) { - return false; - } - return true; - } - return false; - } - - /** - * {@inheritDoc} - * - * <p>This implementation sums a hashcode using all keys and values. - */ - @Override - public int hashCode() { - int result = 0; - for (int i=0; i<mSize; i++) { - K key = keyAt(i); - V value = valueAt(i); - result += (key == null ? 0 : key.hashCode()) - ^ (value == null ? 0 : value.hashCode()); - } - return result; - } - } diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java new file mode 100644 index 0000000..a84c47c --- /dev/null +++ b/core/java/android/util/ArraySet.java @@ -0,0 +1,599 @@ +/* + * Copyright (C) 2013 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.util; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * ArraySet is a generic set data structure that is designed to be more memory efficient than a + * traditional {@link java.util.HashSet}. The design is very similar to + * {@link ArrayMap}, with all of the caveats described there. This implementation is + * separate from ArrayMap, however, so the Object array contains only one item for each + * entry in the set (instead of a pair for a mapping). + * + * <p>Note that this implementation is not intended to be appropriate for data structures + * that may contain large numbers of items. It is generally slower than a traditional + * HashSet, since lookups require a binary search and adds and removes require inserting + * and deleting entries in the array. For containers holding up to hundreds of items, + * the performance difference is not significant, less than 50%. For larger numbers of items + * this data structure should be avoided.</p> + * + * <p><b>Note:</b> unlike {@link java.util.HashSet}, this container does not support + * null values.</p> + * + * <p>Because this container is intended to better balance memory use, unlike most other + * standard Java containers it will shrink its array as items are removed from it. Currently + * you have no control over this shrinking -- if you set a capacity and then remove an + * item, it may reduce the capacity to better match the current size. In the future an + * explicitly call to set the capacity should turn off this aggressive shrinking behavior.</p> + * + * @hide + */ +public final class ArraySet<E> implements Collection<E>, Set<E> { + private static final boolean DEBUG = false; + private static final String TAG = "ArraySet"; + + /** + * The minimum amount by which the capacity of a ArraySet will increase. + * This is tuned to be relatively space-efficient. + */ + private static final int BASE_SIZE = 4; + + /** + * Maximum number of entries to have in array caches. + */ + private static final int CACHE_SIZE = 10; + + /** + * Caches of small array objects to avoid spamming garbage. The cache + * Object[] variable is a pointer to a linked list of array objects. + * The first entry in the array is a pointer to the next array in the + * list; the second entry is a pointer to the int[] hash code array for it. + */ + static Object[] mBaseCache; + static int mBaseCacheSize; + static Object[] mTwiceBaseCache; + static int mTwiceBaseCacheSize; + + int[] mHashes; + Object[] mArray; + int mSize; + MapCollections<E, E> mCollections; + + private int indexOf(Object key, int hash) { + final int N = mSize; + + // Important fast case: if nothing is in here, nothing to look for. + if (N == 0) { + return ~0; + } + + int index = SparseArray.binarySearch(mHashes, N, hash); + + // If the hash code wasn't found, then we have no entry for this key. + if (index < 0) { + return index; + } + + // If the key at the returned index matches, that's what we want. + if (mArray[index].equals(key)) { + return index; + } + + // Search for a matching key after the index. + int end; + for (end = index + 1; end < N && mHashes[end] == hash; end++) { + if (mArray[end].equals(key)) return end; + } + + // Search for a matching key before the index. + for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) { + if (mArray[i].equals(key)) return i; + } + + // Key not found -- return negative value indicating where a + // new entry for this key should go. We use the end of the + // hash chain to reduce the number of array entries that will + // need to be copied when inserting. + return ~end; + } + + private void allocArrays(final int size) { + if (size == (BASE_SIZE*2)) { + synchronized (ArraySet.class) { + if (mTwiceBaseCache != null) { + final Object[] array = mTwiceBaseCache; + mArray = array; + mTwiceBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mTwiceBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes + + " now have " + mTwiceBaseCacheSize + " entries"); + return; + } + } + } else if (size == BASE_SIZE) { + synchronized (ArraySet.class) { + if (mBaseCache != null) { + final Object[] array = mBaseCache; + mArray = array; + mBaseCache = (Object[])array[0]; + mHashes = (int[])array[1]; + array[0] = array[1] = null; + mBaseCacheSize--; + if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes + + " now have " + mBaseCacheSize + " entries"); + return; + } + } + } + + mHashes = new int[size]; + mArray = new Object[size]; + } + + private static void freeArrays(final int[] hashes, final Object[] array, final int size) { + if (hashes.length == (BASE_SIZE*2)) { + synchronized (ArraySet.class) { + if (mTwiceBaseCacheSize < CACHE_SIZE) { + array[0] = mTwiceBaseCache; + array[1] = hashes; + for (int i=size-1; i>=2; i--) { + array[i] = null; + } + mTwiceBaseCache = array; + mTwiceBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 2x cache " + array + + " now have " + mTwiceBaseCacheSize + " entries"); + } + } + } else if (hashes.length == BASE_SIZE) { + synchronized (ArraySet.class) { + if (mBaseCacheSize < CACHE_SIZE) { + array[0] = mBaseCache; + array[1] = hashes; + for (int i=size-1; i>=2; i--) { + array[i] = null; + } + mBaseCache = array; + mBaseCacheSize++; + if (DEBUG) Log.d(TAG, "Storing 1x cache " + array + + " now have " + mBaseCacheSize + " entries"); + } + } + } + } + + /** + * Create a new empty ArraySet. The default capacity of an array map is 0, and + * will grow once items are added to it. + */ + public ArraySet() { + mHashes = SparseArray.EMPTY_INTS; + mArray = SparseArray.EMPTY_OBJECTS; + mSize = 0; + } + + /** + * Create a new ArraySet with a given initial capacity. + */ + public ArraySet(int capacity) { + if (capacity == 0) { + mHashes = SparseArray.EMPTY_INTS; + mArray = SparseArray.EMPTY_OBJECTS; + } else { + allocArrays(capacity); + } + mSize = 0; + } + + /** + * Create a new ArraySet with the mappings from the given ArraySet. + */ + public ArraySet(ArraySet set) { + this(); + if (set != null) { + addAll(set); + } + } + + + /** + * Make the array map empty. All storage is released. + */ + @Override + public void clear() { + if (mSize != 0) { + freeArrays(mHashes, mArray, mSize); + mHashes = SparseArray.EMPTY_INTS; + mArray = SparseArray.EMPTY_OBJECTS; + mSize = 0; + } + } + + /** + * Ensure the array map can hold at least <var>minimumCapacity</var> + * items. + */ + public void ensureCapacity(int minimumCapacity) { + if (mHashes.length < minimumCapacity) { + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(minimumCapacity); + if (mSize > 0) { + System.arraycopy(ohashes, 0, mHashes, 0, mSize); + System.arraycopy(oarray, 0, mArray, 0, mSize); + } + freeArrays(ohashes, oarray, mSize); + } + } + + /** + * Check whether a value exists in the set. + * + * @param key The value to search for. + * @return Returns true if the value exists, else false. + */ + @Override + public boolean contains(Object key) { + return indexOf(key, key.hashCode()) >= 0; + } + + /** + * Return the value at the given index in the array. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value stored at the given index. + */ + public E valueAt(int index) { + return (E)mArray[index]; + } + + /** + * Return true if the array map contains no items. + */ + @Override + public boolean isEmpty() { + return mSize <= 0; + } + + /** + * Adds the specified object to this set. The set is not modified if it + * already contains the object. + * + * @param value the object to add. + * @return {@code true} if this set is modified, {@code false} otherwise. + * @throws ClassCastException + * when the class of the object is inappropriate for this set. + */ + @Override + public boolean add(E value) { + final int hash = value.hashCode(); + int index = indexOf(value, hash); + if (index >= 0) { + return false; + } + + index = ~index; + if (mSize >= mHashes.length) { + final int n = mSize >= (BASE_SIZE*2) ? (mSize+(mSize>>1)) + : (mSize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE); + + if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n); + + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + + if (mHashes.length > 0) { + if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length); + System.arraycopy(oarray, 0, mArray, 0, oarray.length); + } + + freeArrays(ohashes, oarray, mSize); + } + + if (index < mSize) { + if (DEBUG) Log.d(TAG, "add: move " + index + "-" + (mSize-index) + + " to " + (index+1)); + System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); + System.arraycopy(mArray, index, mArray, index + 1, mSize - index); + } + + mHashes[index] = hash; + mArray[index] = value; + mSize++; + return true; + } + + /** + * Perform a {@link #add(Object)} of all values in <var>array</var> + * @param array The array whose contents are to be retrieved. + */ + public void putAll(ArraySet<? extends E> array) { + final int N = array.mSize; + ensureCapacity(mSize + N); + if (mSize == 0) { + if (N > 0) { + System.arraycopy(array.mHashes, 0, mHashes, 0, N); + System.arraycopy(array.mArray, 0, mArray, 0, N); + mSize = N; + } + } else { + for (int i=0; i<N; i++) { + add(array.valueAt(i)); + } + } + } + + /** + * Removes the specified object from this set. + * + * @param object the object to remove. + * @return {@code true} if this set was modified, {@code false} otherwise. + */ + @Override + public boolean remove(Object object) { + int index = indexOf(object, object.hashCode()); + if (index >= 0) { + removeAt(index); + return true; + } + return false; + } + + /** + * Remove the key/value mapping at the given index. + * @param index The desired index, must be between 0 and {@link #size()}-1. + * @return Returns the value that was stored at this index. + */ + public E removeAt(int index) { + final E old = (E)mArray[index]; + if (mSize <= 1) { + // Now empty. + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0"); + freeArrays(mHashes, mArray, mSize); + mHashes = SparseArray.EMPTY_INTS; + mArray = SparseArray.EMPTY_OBJECTS; + mSize = 0; + } else { + if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) { + // Shrunk enough to reduce size of arrays. We don't allow it to + // shrink smaller than (BASE_SIZE*2) to avoid flapping between + // that and BASE_SIZE. + final int n = mSize > (BASE_SIZE*2) ? (mSize + (mSize>>1)) : (BASE_SIZE*2); + + if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n); + + final int[] ohashes = mHashes; + final Object[] oarray = mArray; + allocArrays(n); + + mSize--; + if (index > 0) { + if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0"); + System.arraycopy(ohashes, 0, mHashes, 0, index); + System.arraycopy(oarray, 0, mArray, 0, index); + } + if (index < mSize) { + if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + mSize + + " to " + index); + System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index); + System.arraycopy(oarray, index + 1, mArray, index, mSize - index); + } + } else { + mSize--; + if (index < mSize) { + if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + mSize + + " to " + index); + System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index); + System.arraycopy(mArray, index + 1, mArray, index, mSize - index); + } + mArray[mSize] = null; + } + } + return old; + } + + /** + * Return the number of items in this array map. + */ + @Override + public int size() { + return mSize; + } + + @Override + public Object[] toArray() { + Object[] result = new Object[mSize]; + System.arraycopy(mArray, 0, result, 0, mSize); + return result; + } + + @Override + public <T> T[] toArray(T[] array) { + if (array.length < mSize) { + @SuppressWarnings("unchecked") T[] newArray + = (T[]) Array.newInstance(array.getClass().getComponentType(), mSize); + array = newArray; + } + System.arraycopy(mArray, 0, array, 0, mSize); + if (array.length > mSize) { + array[mSize] = null; + } + return array; + } + + /** + * {@inheritDoc} + * + * <p>This implementation returns false if the object is not a set, or + * if the sets have different sizes. Otherwise, for each value in this + * set, it checks to make sure the value also exists in the other set. + * If any value doesn't exist, the method returns false; otherwise, it + * returns true. + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Set) { + Set<?> set = (Set<?>) object; + if (size() != set.size()) { + return false; + } + + try { + for (int i=0; i<mSize; i++) { + E mine = valueAt(i); + if (!set.contains(mine)) { + return false; + } + } + } catch (NullPointerException ignored) { + return false; + } catch (ClassCastException ignored) { + return false; + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int[] hashes = mHashes; + int result = 0; + for (int i = 0, s = mSize; i < s; i++) { + result += hashes[i]; + } + return result; + } + + // ------------------------------------------------------------------------ + // Interop with traditional Java containers. Not as efficient as using + // specialized collection APIs. + // ------------------------------------------------------------------------ + + private MapCollections<E, E> getCollection() { + if (mCollections == null) { + mCollections = new MapCollections<E, E>() { + @Override + protected int colGetSize() { + return mSize; + } + + @Override + protected Object colGetEntry(int index, int offset) { + return mArray[index]; + } + + @Override + protected int colIndexOfKey(Object key) { + return indexOf(key, key.hashCode()); + } + + @Override + protected int colIndexOfValue(Object value) { + return indexOf(value, value.hashCode()); + } + + @Override + protected Map<E, E> colGetMap() { + throw new UnsupportedOperationException("not a map"); + } + + @Override + protected void colPut(E key, E value) { + add(key); + } + + @Override + protected E colSetValue(int index, E value) { + throw new UnsupportedOperationException("not a map"); + } + + @Override + protected void colRemoveAt(int index) { + removeAt(index); + } + + @Override + protected void colClear() { + clear(); + } + }; + } + return mCollections; + } + + @Override + public Iterator<E> iterator() { + return getCollection().getKeySet().iterator(); + } + + @Override + public boolean containsAll(Collection<?> collection) { + Iterator<?> it = collection.iterator(); + while (it.hasNext()) { + if (!contains(it.next())) { + return false; + } + } + return true; + } + + @Override + public boolean addAll(Collection<? extends E> collection) { + ensureCapacity(mSize + collection.size()); + boolean added = false; + for (E value : collection) { + added |= add(value); + } + return added; + } + + @Override + public boolean removeAll(Collection<?> collection) { + boolean removed = false; + for (Object value : collection) { + removed |= remove(value); + } + return removed; + } + + @Override + public boolean retainAll(Collection<?> collection) { + boolean removed = false; + for (int i=mSize-1; i>=0; i--) { + if (!collection.contains(mArray[i])) { + removeAt(i); + removed = true; + } + } + return removed; + } +} diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 6291e62..c735dd7 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -408,11 +408,6 @@ public interface WindowManagerPolicy { public int getLidState(); /** - * Creates an input channel that will receive all input from the input dispatcher. - */ - public InputChannel monitorInput(String name); - - /** * Switch the keyboard layout for the given device. * Direction should be +1 or -1 to go to the next or previous keyboard layout. */ @@ -425,6 +420,21 @@ public interface WindowManagerPolicy { * Return the window manager lock needed to correctly call "Lw" methods. */ public Object getWindowManagerLock(); + + /** Register a system listener for touch events */ + void registerPointerEventListener(PointerEventListener listener); + + /** Unregister a system listener for touch events */ + void unregisterPointerEventListener(PointerEventListener listener); + } + + public interface PointerEventListener { + /** + * 1. onPointerEvent will be called on the service.UiThread. + * 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a + * copy() must be made and the copy must be recycled. + **/ + public void onPointerEvent(MotionEvent motionEvent); } /** Window has been added to the screen. */ diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index fc9c000..aa33384 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -18,6 +18,7 @@ package android.widget; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; @@ -37,12 +38,13 @@ import android.widget.AbsListView.OnScrollListener; */ class FastScroller { private static final String TAG = "FastScroller"; - + // Minimum number of pages to justify showing a fast scroll thumb private static int MIN_PAGES = 4; // Scroll thumb not showing private static final int STATE_NONE = 0; // Not implemented yet - fade-in transition + @SuppressWarnings("unused") private static final int STATE_ENTER = 1; // Scroll thumb visible and moving along with the scrollbar private static final int STATE_VISIBLE = 2; @@ -75,7 +77,7 @@ class FastScroller { private static final int OVERLAY_FLOATING = 0; private static final int OVERLAY_AT_THUMB = 1; - + private Drawable mThumbDrawable; private Drawable mOverlayDrawable; private Drawable mTrackDrawable; @@ -89,6 +91,7 @@ class FastScroller { private RectF mOverlayPos; private int mOverlaySize; + private int mOverlayPadding; AbsListView mList; boolean mScrollCompleted; @@ -97,21 +100,21 @@ class FastScroller { private int mListOffset; private int mItemCount = -1; private boolean mLongList; - + private Object [] mSections; private String mSectionText; private boolean mDrawOverlay; private ScrollFade mScrollFade; - + private int mState; - + private Handler mHandler = new Handler(); - + BaseAdapter mListAdapter; private SectionIndexer mSectionIndexer; private boolean mChangedBounds; - + private int mPosition; private boolean mAlwaysShow; @@ -130,6 +133,7 @@ class FastScroller { private final Rect mTmpRect = new Rect(); private final Runnable mDeferStartDrag = new Runnable() { + @Override public void run() { if (mList.mIsAttached) { beginDrag(); @@ -237,11 +241,11 @@ class FastScroller { mState = state; refreshDrawableState(); } - + public int getState() { return mState; } - + private void resetThumbPos() { final int viewWidth = mList.getWidth(); // Bounds are always top right. Y coordinate get's translated during draw @@ -255,7 +259,7 @@ class FastScroller { } mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX); } - + private void useThumbDrawable(Context context, Drawable drawable) { mThumbDrawable = drawable; if (drawable instanceof NinePatchDrawable) { @@ -272,20 +276,23 @@ class FastScroller { private void init(Context context) { // Get both the scrollbar states drawables - TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS); + final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS); useThumbDrawable(context, ta.getDrawable(THUMB_DRAWABLE)); mTrackDrawable = ta.getDrawable(TRACK_DRAWABLE); - + mOverlayDrawableLeft = ta.getDrawable(PREVIEW_BACKGROUND_LEFT); mOverlayDrawableRight = ta.getDrawable(PREVIEW_BACKGROUND_RIGHT); mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING); - + mScrollCompleted = true; getSectionsFromIndexer(); - mOverlaySize = context.getResources().getDimensionPixelSize( + final Resources res = context.getResources(); + mOverlaySize = res.getDimensionPixelSize( com.android.internal.R.dimen.fastscroll_overlay_size); + mOverlayPadding = res.getDimensionPixelSize( + com.android.internal.R.dimen.fastscroll_overlay_padding); mOverlayPos = new RectF(); mScrollFade = new ScrollFade(); mPaint = new Paint(); @@ -302,7 +309,7 @@ class FastScroller { if (mList.getWidth() > 0 && mList.getHeight() > 0) { onSizeChanged(mList.getWidth(), mList.getHeight(), 0, 0); } - + mState = STATE_NONE; refreshDrawableState(); @@ -315,17 +322,17 @@ class FastScroller { setScrollbarPosition(mList.getVerticalScrollbarPosition()); } - + void stop() { setState(STATE_NONE); } - + boolean isVisible() { return !(mState == STATE_NONE); } - + public void draw(Canvas canvas) { - + if (mState == STATE_NONE) { // No need to draw anything return; @@ -371,44 +378,53 @@ class FastScroller { // If user is dragging the scroll bar, draw the alphabet overlay if (mState == STATE_DRAGGING && mDrawOverlay) { + final Drawable overlay = mOverlayDrawable; + final Paint paint = mPaint; + final String sectionText = mSectionText; + final Rect tmpRect = mTmpRect; + + // TODO: Use a text view in an overlay for transition animations and + // handling of text overflow. + paint.getTextBounds(sectionText, 0, sectionText.length(), tmpRect); + final int textWidth = tmpRect.width(); + final int textHeight = tmpRect.height(); + + overlay.getPadding(tmpRect); + final int overlayWidth = Math.max( + mOverlaySize, textWidth + tmpRect.left + tmpRect.right + mOverlayPadding * 2); + final int overlayHeight = Math.max( + mOverlaySize, textHeight + tmpRect.top + tmpRect.bottom + mOverlayPadding * 2); + final RectF pos = mOverlayPos; + if (mOverlayPosition == OVERLAY_AT_THUMB) { - int left = 0; + final Rect thumbBounds = mThumbDrawable.getBounds(); + switch (mPosition) { - default: - case View.SCROLLBAR_POSITION_RIGHT: - left = Math.max(0, - mThumbDrawable.getBounds().left - mThumbW - mOverlaySize); - break; case View.SCROLLBAR_POSITION_LEFT: - left = Math.min(mThumbDrawable.getBounds().right + mThumbW, - mList.getWidth() - mOverlaySize); + pos.left = Math.min( + thumbBounds.right + mThumbW, mList.getWidth() - overlayWidth); + break; + case View.SCROLLBAR_POSITION_RIGHT: + default: + pos.left = Math.max(0, thumbBounds.left - mThumbW - overlayWidth); break; } - int top = Math.max(0, - Math.min(y + (mThumbH - mOverlaySize) / 2, mList.getHeight() - mOverlaySize)); - - final RectF pos = mOverlayPos; - pos.left = left; - pos.right = pos.left + mOverlaySize; - pos.top = top; - pos.bottom = pos.top + mOverlaySize; - if (mOverlayDrawable != null) { - mOverlayDrawable.setBounds((int) pos.left, (int) pos.top, - (int) pos.right, (int) pos.bottom); - } + pos.top = Math.max(0, Math.min( + y + (mThumbH - overlayHeight) / 2, mList.getHeight() - overlayHeight)); } - mOverlayDrawable.draw(canvas); - final Paint paint = mPaint; - float descent = paint.descent(); - final RectF rectF = mOverlayPos; - final Rect tmpRect = mTmpRect; - mOverlayDrawable.getPadding(tmpRect); - final int hOff = (tmpRect.right - tmpRect.left) / 2; - final int vOff = (tmpRect.bottom - tmpRect.top) / 2; - canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2 - hOff, - (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent - vOff, - paint); + + pos.right = pos.left + overlayWidth; + pos.bottom = pos.top + overlayHeight; + + overlay.setBounds((int) pos.left, (int) pos.top, (int) pos.right, (int) pos.bottom); + overlay.draw(canvas); + + final float hOff = (tmpRect.right - tmpRect.left) / 2.0f; + final float vOff = (tmpRect.bottom - tmpRect.top) / 2.0f; + final float cX = pos.centerX() - hOff; + final float cY = pos.centerY() + (overlayHeight / 4.0f) - paint.descent() - vOff; + canvas.drawText(mSectionText, cX, cY, paint); } else if (mState == STATE_EXIT) { if (alpha == 0) { // Done with exit setState(STATE_NONE); @@ -467,7 +483,7 @@ class FastScroller { } } - void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // Are there enough pages to require fast scroll? Recompute only if total count changes if (mItemCount != totalItemCount && visibleItemCount > 0) { @@ -577,7 +593,7 @@ class FastScroller { if (section < nSections - 1) { nextIndex = mSectionIndexer.getPositionForSection(section + 1); } - + // Find the previous index if we're slicing the previous section if (nextIndex == index) { // Non-existent letter @@ -597,9 +613,9 @@ class FastScroller { } } // Find the next index, in case the assumed next index is not - // unique. For instance, if there is no P, then request for P's + // unique. For instance, if there is no P, then request for P's // position actually returns Q's. So we need to look ahead to make - // sure that there is really a Q at Q's position. If not, move + // sure that there is really a Q at Q's position. If not, move // further down... int nextNextSection = nextSection + 1; while (nextNextSection < nSections && @@ -609,18 +625,18 @@ class FastScroller { } // Compute the beginning and ending scroll range percentage of the // currently visible letter. This could be equal to or greater than - // (1 / nSections). + // (1 / nSections). float fPrev = (float) prevSection / nSections; float fNext = (float) nextSection / nSections; if (prevSection == exactSection && position - fPrev < fThreshold) { index = prevIndex; } else { - index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev) + index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev) / (fNext - fPrev)); } // Don't overflow if (index > count - 1) index = count - 1; - + if (mList instanceof ExpandableListView) { ExpandableListView expList = (ExpandableListView) mList; expList.setSelectionFromTop(expList.getFlatListPosition( @@ -705,7 +721,7 @@ class FastScroller { mList.onTouchEvent(cancelFling); cancelFling.recycle(); } - + void cancelPendingDrag() { mList.removeCallbacks(mDeferStartDrag); mPendingDrag = false; @@ -862,18 +878,18 @@ class FastScroller { } public class ScrollFade implements Runnable { - + long mStartTime; long mFadeDuration; static final int ALPHA_MAX = 208; static final long FADE_DURATION = 200; - + void startFade() { mFadeDuration = FADE_DURATION; mStartTime = SystemClock.uptimeMillis(); setState(STATE_EXIT); } - + int getAlpha() { if (getState() != STATE_EXIT) { return ALPHA_MAX; @@ -883,17 +899,18 @@ class FastScroller { if (now > mStartTime + mFadeDuration) { alpha = 0; } else { - alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration); + alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration); } return alpha; } - + + @Override public void run() { if (getState() != STATE_EXIT) { startFade(); return; } - + if (getAlpha() > 0) { mList.invalidate(); } else { diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index 738f63b..691c941 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -267,12 +267,12 @@ public class FrameLayout extends ViewGroup { return mForeground; } - private int getPaddingLeftWithForeground() { + int getPaddingLeftWithForeground() { return mForegroundInPadding ? Math.max(mPaddingLeft, mForegroundPaddingLeft) : mPaddingLeft + mForegroundPaddingLeft; } - private int getPaddingRightWithForeground() { + int getPaddingRightWithForeground() { return mForegroundInPadding ? Math.max(mPaddingRight, mForegroundPaddingRight) : mPaddingRight + mForegroundPaddingRight; } @@ -385,6 +385,11 @@ public class FrameLayout extends ViewGroup { */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + layoutChildren(left, top, right, bottom, false /* no force left gravity */); + } + + void layoutChildren(int left, int top, int right, int bottom, + boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); @@ -416,16 +421,16 @@ public class FrameLayout extends ViewGroup { final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { - case Gravity.LEFT: - childLeft = parentLeft + lp.leftMargin; - break; case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: - childLeft = parentRight - width - lp.rightMargin; - break; + if (!forceLeftGravity) { + childLeft = parentRight - width - lp.rightMargin; + break; + } + case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 55cfd27..61e791a 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -21,6 +21,8 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.view.FocusFinder; @@ -133,6 +135,8 @@ public class HorizontalScrollView extends FrameLayout { */ private static final int INVALID_POINTER = -1; + private SavedState mSavedState; + public HorizontalScrollView(Context context) { this(context, null); } @@ -1452,7 +1456,14 @@ public class HorizontalScrollView extends FrameLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); + // There is only one child + final View child = getChildAt(0); + final int childWidth = child.getMeasuredWidth(); + final LayoutParams childParams = (LayoutParams) child.getLayoutParams(); + final int available = r - l - getPaddingLeftWithForeground() - + getPaddingRightWithForeground() - childParams.leftMargin - childParams.rightMargin; + final boolean forceLeftGravity = (childWidth > available); + layoutChildren(l, t, r, b, forceLeftGravity); mIsLayoutDirty = false; // Give a child focus if it needs it if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) { @@ -1460,6 +1471,28 @@ public class HorizontalScrollView extends FrameLayout { } mChildToScrollTo = null; + if (!hasLayout()) { + final int scrollRange = Math.max(0, + childWidth - (r - l - mPaddingLeft - mPaddingRight)); + if (mSavedState != null) { + if (isLayoutRtl() == mSavedState.isLayoutRtl) { + mScrollX = mSavedState.scrollPosition; + } else { + mScrollX = scrollRange - mSavedState.scrollPosition; + } + } else { + if (isLayoutRtl()) { + mScrollX = scrollRange - mScrollX; + } // mScrollX default value is "0" for LTR + } + // Don't forget to clamp + if (mScrollX > scrollRange) { + mScrollX = scrollRange; + } else if (mScrollX < 0) { + mScrollX = 0; + } + } + // Calling this with the present values causes it to re-claim them scrollTo(mScrollX, mScrollY); } @@ -1604,4 +1637,62 @@ public class HorizontalScrollView extends FrameLayout { } return n; } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mSavedState = ss; + requestLayout(); + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.scrollPosition = mScrollX; + ss.isLayoutRtl = isLayoutRtl(); + return ss; + } + + static class SavedState extends BaseSavedState { + public int scrollPosition; + public boolean isLayoutRtl; + + SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + scrollPosition = source.readInt(); + isLayoutRtl = (source.readInt() == 0) ? true : false; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(scrollPosition); + dest.writeInt(isLayoutRtl ? 1 : 0); + } + + @Override + public String toString() { + return "HorizontalScrollView.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " scrollPosition=" + scrollPosition + + " isLayoutRtl=" + isLayoutRtl + "}"; + } + + public static final Parcelable.Creator<SavedState> CREATOR + = new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 001d99c..ee22811 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -240,35 +240,39 @@ public class ListView extends AbsListView { } /** - * Add a fixed view to appear at the top of the list. If addHeaderView is + * Add a fixed view to appear at the top of the list. If this method is * called more than once, the views will appear in the order they were * added. Views added using this call can take focus if they want. * <p> - * NOTE: Call this before calling setAdapter. This is so ListView can wrap - * the supplied cursor with one that will also account for header and footer - * views. + * Note: When first introduced, this method could only be called before + * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with + * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, this method may be + * called at any time. If the ListView's adapter does not extend + * {@link HeaderViewListAdapter}, it will be wrapped with a supporting + * instance of {@link WrapperListAdapter}. * * @param v The view to add. * @param data Data to associate with this view * @param isSelectable whether the item is selectable */ public void addHeaderView(View v, Object data, boolean isSelectable) { - - if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) { - throw new IllegalStateException( - "Cannot add header view to list -- setAdapter has already been called."); - } - - FixedViewInfo info = new FixedViewInfo(); + final FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mHeaderViewInfos.add(info); - // in the case of re-adding a header view, or adding one later on, - // we need to notify the observer - if (mAdapter != null && mDataSetObserver != null) { - mDataSetObserver.onChanged(); + // Wrap the adapter if it wasn't already wrapped. + if (mAdapter != null) { + if (!(mAdapter instanceof HeaderViewListAdapter)) { + mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter); + } + + // In the case of re-adding a header view, or adding one later on, + // we need to notify the observer. + if (mDataSetObserver != null) { + mDataSetObserver.onChanged(); + } } } @@ -277,9 +281,12 @@ public class ListView extends AbsListView { * called more than once, the views will appear in the order they were * added. Views added using this call can take focus if they want. * <p> - * NOTE: Call this before calling setAdapter. This is so ListView can wrap - * the supplied cursor with one that will also account for header and footer - * views. + * Note: When first introduced, this method could only be called before + * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with + * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, this method may be + * called at any time. If the ListView's adapter does not extend + * {@link HeaderViewListAdapter}, it will be wrapped with a supporting + * instance of {@link WrapperListAdapter}. * * @param v The view to add. */ @@ -330,41 +337,49 @@ public class ListView extends AbsListView { * called more than once, the views will appear in the order they were * added. Views added using this call can take focus if they want. * <p> - * NOTE: Call this before calling setAdapter. This is so ListView can wrap - * the supplied cursor with one that will also account for header and footer - * views. + * Note: When first introduced, this method could only be called before + * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with + * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, this method may be + * called at any time. If the ListView's adapter does not extend + * {@link HeaderViewListAdapter}, it will be wrapped with a supporting + * instance of {@link WrapperListAdapter}. * * @param v The view to add. * @param data Data to associate with this view * @param isSelectable true if the footer view can be selected */ public void addFooterView(View v, Object data, boolean isSelectable) { - - // NOTE: do not enforce the adapter being null here, since unlike in - // addHeaderView, it was never enforced here, and so existing apps are - // relying on being able to add a footer and then calling setAdapter to - // force creation of the HeaderViewListAdapter wrapper - - FixedViewInfo info = new FixedViewInfo(); + final FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; info.isSelectable = isSelectable; mFooterViewInfos.add(info); - // in the case of re-adding a footer view, or adding one later on, - // we need to notify the observer - if (mAdapter != null && mDataSetObserver != null) { - mDataSetObserver.onChanged(); + // Wrap the adapter if it wasn't already wrapped. + if (mAdapter != null) { + if (!(mAdapter instanceof HeaderViewListAdapter)) { + mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter); + } + + // In the case of re-adding a footer view, or adding one later on, + // we need to notify the observer. + if (mDataSetObserver != null) { + mDataSetObserver.onChanged(); + } } } /** - * Add a fixed view to appear at the bottom of the list. If addFooterView is called more - * than once, the views will appear in the order they were added. Views added using - * this call can take focus if they want. - * <p>NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied - * cursor with one that will also account for header and footer views. - * + * Add a fixed view to appear at the bottom of the list. If addFooterView is + * called more than once, the views will appear in the order they were + * added. Views added using this call can take focus if they want. + * <p> + * Note: When first introduced, this method could only be called before + * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with + * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE}, this method may be + * called at any time. If the ListView's adapter does not extend + * {@link HeaderViewListAdapter}, it will be wrapped with a supporting + * instance of {@link WrapperListAdapter}. * * @param v The view to add. */ @@ -3433,6 +3448,7 @@ public class ListView extends AbsListView { * @param headerDividersEnabled True to draw the headers, false otherwise. * * @see #setFooterDividersEnabled(boolean) + * @see #areHeaderDividersEnabled() * @see #addHeaderView(android.view.View) */ public void setHeaderDividersEnabled(boolean headerDividersEnabled) { @@ -3441,17 +3457,36 @@ public class ListView extends AbsListView { } /** + * @return Whether the drawing of the divider for header views is enabled + * + * @see #setHeaderDividersEnabled(boolean) + */ + public boolean areHeaderDividersEnabled() { + return mHeaderDividersEnabled; + } + + /** * Enables or disables the drawing of the divider for footer views. * * @param footerDividersEnabled True to draw the footers, false otherwise. * * @see #setHeaderDividersEnabled(boolean) + * @see #areFooterDividersEnabled() * @see #addFooterView(android.view.View) */ public void setFooterDividersEnabled(boolean footerDividersEnabled) { mFooterDividersEnabled = footerDividersEnabled; invalidate(); } + + /** + * @return Whether the drawing of the divider for footer views is enabled + * + * @see #setFooterDividersEnabled(boolean) + */ + public boolean areFooterDividersEnabled() { + return mFooterDividersEnabled; + } /** * Sets the drawable that will be drawn above all other list content. diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index f10a2e8..52c2cdd 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -549,8 +549,9 @@ public class PointerLocationView extends View implements InputDeviceListener { final PointerState ps = mPointers.get(id); ps.mCurDown = true; - ps.mHasBoundingBox = (InputDevice.getDevice(event.getDeviceId()). - getMotionRange(MotionEvent.AXIS_GENERIC_1) != null); + InputDevice device = InputDevice.getDevice(event.getDeviceId()); + ps.mHasBoundingBox = device != null && + device.getMotionRange(MotionEvent.AXIS_GENERIC_1) != null; } final int NI = event.getPointerCount(); |
