diff options
Diffstat (limited to 'core/java/android/view')
68 files changed, 29752 insertions, 0 deletions
diff --git a/core/java/android/view/AbsSavedState.java b/core/java/android/view/AbsSavedState.java new file mode 100644 index 0000000..840d7c1 --- /dev/null +++ b/core/java/android/view/AbsSavedState.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2006 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.view; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A {@link Parcelable} implementation that should be used by inheritance + * hierarchies to ensure the state of all classes along the chain is saved. + */ +public abstract class AbsSavedState implements Parcelable { + public static final AbsSavedState EMPTY_STATE = new AbsSavedState() {}; + + private final Parcelable mSuperState; + + /** + * Constructor used to make the EMPTY_STATE singleton + */ + private AbsSavedState() { + mSuperState = null; + } + + /** + * Constructor called by derived classes when creating their SavedState objects + * + * @param superState The state of the superclass of this view + */ + protected AbsSavedState(Parcelable superState) { + if (superState == null) { + throw new IllegalArgumentException("superState must not be null"); + } + mSuperState = superState != EMPTY_STATE ? superState : null; + } + + /** + * Constructor used when reading from a parcel. Reads the state of the superclass. + * + * @param source + */ + protected AbsSavedState(Parcel source) { + // FIXME need class loader + Parcelable superState = (Parcelable) source.readParcelable(null); + + mSuperState = superState != null ? superState : EMPTY_STATE; + } + + final public Parcelable getSuperState() { + return mSuperState; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mSuperState, flags); + } + + public static final Parcelable.Creator<AbsSavedState> CREATOR + = new Parcelable.Creator<AbsSavedState>() { + + public AbsSavedState createFromParcel(Parcel in) { + Parcelable superState = (Parcelable) in.readParcelable(null); + if (superState != null) { + throw new IllegalStateException("superState must be null"); + } + return EMPTY_STATE; + } + + public AbsSavedState[] newArray(int size) { + return new AbsSavedState[size]; + } + }; +} diff --git a/core/java/android/view/ContextMenu.java b/core/java/android/view/ContextMenu.java new file mode 100644 index 0000000..9bfda40 --- /dev/null +++ b/core/java/android/view/ContextMenu.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 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.view; + +import android.app.Activity; +import android.graphics.drawable.Drawable; +import android.widget.AdapterView; + +/** + * Extension of {@link Menu} for context menus providing functionality to modify + * the header of the context menu. + * <p> + * Context menus do not support item shortcuts, item icons, and sub menus. + * <p> + * To show a context menu on long click, most clients will want to call + * {@link Activity#registerForContextMenu} and override + * {@link Activity#onCreateContextMenu}. + */ +public interface ContextMenu extends Menu { + /** + * Sets the context menu header's title to the title given in <var>titleRes</var> + * resource identifier. + * + * @param titleRes The string resource identifier used for the title. + * @return This ContextMenu so additional setters can be called. + */ + public ContextMenu setHeaderTitle(int titleRes); + + /** + * Sets the context menu header's title to the title given in <var>title</var>. + * + * @param title The character sequence used for the title. + * @return This ContextMenu so additional setters can be called. + */ + public ContextMenu setHeaderTitle(CharSequence title); + + /** + * Sets the context menu header's icon to the icon given in <var>iconRes</var> + * resource id. + * + * @param iconRes The resource identifier used for the icon. + * @return This ContextMenu so additional setters can be called. + */ + public ContextMenu setHeaderIcon(int iconRes); + + /** + * Sets the context menu header's icon to the icon given in <var>icon</var> + * {@link Drawable}. + * + * @param icon The {@link Drawable} used for the icon. + * @return This ContextMenu so additional setters can be called. + */ + public ContextMenu setHeaderIcon(Drawable icon); + + /** + * Sets the header of the context menu to the {@link View} given in + * <var>view</var>. This replaces the header title and icon (and those + * replace this). + * + * @param view The {@link View} used for the header. + * @return This ContextMenu so additional setters can be called. + */ + public ContextMenu setHeaderView(View view); + + /** + * Clears the header of the context menu. + */ + public void clearHeader(); + + /** + * Additional information regarding the creation of the context menu. For example, + * {@link AdapterView}s use this to pass the exact item position within the adapter + * that initiated the context menu. + */ + public interface ContextMenuInfo { + } +} diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java new file mode 100644 index 0000000..2045a98 --- /dev/null +++ b/core/java/android/view/ContextThemeWrapper.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2006 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.view; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; + +/** + * A ContextWrapper that allows you to modify the theme from what is in the + * wrapped context. + */ +public class ContextThemeWrapper extends ContextWrapper { + private Context mBase; + private int mThemeResource; + private Resources.Theme mTheme; + private LayoutInflater mInflater; + + public ContextThemeWrapper() { + super(null); + } + + public ContextThemeWrapper(Context base, int themeres) { + super(base); + mBase = base; + mThemeResource = themeres; + } + + @Override protected void attachBaseContext(Context newBase) { + super.attachBaseContext(newBase); + mBase = newBase; + } + + @Override public void setTheme(int resid) { + mThemeResource = resid; + initializeTheme(); + } + + @Override public Resources.Theme getTheme() { + if (mTheme != null) { + return mTheme; + } + + if (mThemeResource == 0) { + mThemeResource = com.android.internal.R.style.Theme; + } + initializeTheme(); + + return mTheme; + } + + @Override public Object getSystemService(String name) { + if (LAYOUT_INFLATER_SERVICE.equals(name)) { + if (mInflater == null) { + mInflater = LayoutInflater.from(mBase).cloneInContext(this); + } + return mInflater; + } + return mBase.getSystemService(name); + } + + /** + * Called by {@link #setTheme} and {@link #getTheme} to apply a theme + * resource to the current Theme object. Can override to change the + * default (simple) behavior. This method will not be called in multiple + * threads simultaneously. + * + * @param theme The Theme object being modified. + * @param resid The theme style resource being applied to <var>theme</var>. + * @param first Set to true if this is the first time a style is being + * applied to <var>theme</var>. + */ + protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) { + theme.applyStyle(resid, true); + } + + private void initializeTheme() { + final boolean first = mTheme == null; + if (first) { + mTheme = getResources().newTheme(); + Resources.Theme theme = mBase.getTheme(); + if (theme != null) { + mTheme.setTo(theme); + } + } + onApplyThemeResource(mTheme, mThemeResource, first); + } +} + diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java new file mode 100644 index 0000000..09ebeed --- /dev/null +++ b/core/java/android/view/Display.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2006 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.view; + +import android.util.DisplayMetrics; + +public class Display +{ + /** + * Specify the default Display + */ + public static final int DEFAULT_DISPLAY = 0; + + + /** + * Use the WindowManager interface to create a Display object. + * Display gives you access to some information about a particular display + * connected to the device. + */ + Display(int display) { + // initalize the statics when this class is first instansiated. This is + // done here instead of in the static block because Zygote + synchronized (mStaticInit) { + if (!mInitialized) { + nativeClassInit(); + mInitialized = true; + } + } + mDisplay = display; + init(display); + } + + /** + * @return index of this display. + */ + public int getDisplayId() { + return mDisplay; + } + + /** + * @return the number of displays connected to the device. + */ + native static int getDisplayCount(); + + /** + * @return width of this display in pixels. + */ + native public int getWidth(); + + /** + * @return height of this display in pixels. + */ + native public int getHeight(); + + /** + * @return orientation of this display. + */ + native public int getOrientation(); + + /** + * @return pixel format of this display. + */ + public int getPixelFormat() { + return mPixelFormat; + } + + /** + * @return refresh rate of this display in frames per second. + */ + public float getRefreshRate() { + return mRefreshRate; + } + + /** + * Initialize a DisplayMetrics object from this display's data. + * + * @param outMetrics + */ + public void getMetrics(DisplayMetrics outMetrics) { + outMetrics.widthPixels = getWidth(); + outMetrics.heightPixels = getHeight(); + outMetrics.density = mDensity; + outMetrics.scaledDensity= outMetrics.density; + outMetrics.xdpi = mDpiX; + outMetrics.ydpi = mDpiY; + } + + /* + * We use a class initializer to allow the native code to cache some + * field offsets. + */ + native private static void nativeClassInit(); + + private native void init(int display); + + private int mDisplay; + // Following fields are initialized from native code + private int mPixelFormat; + private float mRefreshRate; + private float mDensity; + private float mDpiX; + private float mDpiY; + + private static final Object mStaticInit = new Object(); + private static boolean mInitialized = false; +} + diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java new file mode 100644 index 0000000..4048763 --- /dev/null +++ b/core/java/android/view/FocusFinder.java @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2007 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.view; + +import android.graphics.Rect; + +import java.util.ArrayList; + +/** + * The algorithm used for finding the next focusable view in a given direction + * from a view that currently has focus. + */ +public class FocusFinder { + + private static ThreadLocal<FocusFinder> tlFocusFinder = + new ThreadLocal<FocusFinder>() { + + protected FocusFinder initialValue() { + return new FocusFinder(); + } + }; + + /** + * Get the focus finder for this thread. + */ + public static FocusFinder getInstance() { + return tlFocusFinder.get(); + } + + Rect mFocusedRect = new Rect(); + Rect mOtherRect = new Rect(); + Rect mBestCandidateRect = new Rect(); + + // enforce thread local access + private FocusFinder() {} + + /** + * Find the next view to take focus in root's descendants, starting from the view + * that currently is focused. + * @param root Contains focused + * @param focused Has focus now. + * @param direction Direction to look. + * @return The next focusable view, or null if none exists. + */ + public final View findNextFocus(ViewGroup root, View focused, int direction) { + + if (focused != null) { + // check for user specified next focus + View userSetNextFocus = focused.findUserSetNextFocus(root, direction); + if (userSetNextFocus != null && + userSetNextFocus.isFocusable() && + (!userSetNextFocus.isInTouchMode() || + userSetNextFocus.isFocusableInTouchMode())) { + return userSetNextFocus; + } + + // fill in interesting rect from focused + focused.getFocusedRect(mFocusedRect); + root.offsetDescendantRectToMyCoords(focused, mFocusedRect); + } else { + // make up a rect at top left or bottom right of root + switch (direction) { + case View.FOCUS_RIGHT: + case View.FOCUS_DOWN: + final int rootTop = root.getScrollY(); + final int rootLeft = root.getScrollX(); + mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop); + break; + + case View.FOCUS_LEFT: + case View.FOCUS_UP: + final int rootBottom = root.getScrollY() + root.getHeight(); + final int rootRight = root.getScrollX() + root.getWidth(); + mFocusedRect.set(rootRight, rootBottom, + rootRight, rootBottom); + break; + } + } + return findNextFocus(root, focused, mFocusedRect, direction); + } + + /** + * Find the next view to take focus in root's descendants, searching from + * a particular rectangle in root's coordinates. + * @param root Contains focusedRect. + * @param focusedRect The starting point of the search. + * @param direction Direction to look. + * @return The next focusable view, or null if none exists. + */ + public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) { + return findNextFocus(root, null, focusedRect, direction); + } + + private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) { + ArrayList<View> focusables = root.getFocusables(direction); + + // initialize the best candidate to something impossible + // (so the first plausible view will become the best choice) + mBestCandidateRect.set(focusedRect); + switch(direction) { + case View.FOCUS_LEFT: + mBestCandidateRect.offset(focusedRect.width() + 1, 0); + break; + case View.FOCUS_RIGHT: + mBestCandidateRect.offset(-(focusedRect.width() + 1), 0); + break; + case View.FOCUS_UP: + mBestCandidateRect.offset(0, focusedRect.height() + 1); + break; + case View.FOCUS_DOWN: + mBestCandidateRect.offset(0, -(focusedRect.height() + 1)); + } + + View closest = null; + + int numFocusables = focusables.size(); + for (int i = 0; i < numFocusables; i++) { + View focusable = focusables.get(i); + + // only interested in other non-root views + if (focusable == focused || focusable == root) continue; + + // get visible bounds of other view in same coordinate system + focusable.getDrawingRect(mOtherRect); + root.offsetDescendantRectToMyCoords(focusable, mOtherRect); + + if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) { + mBestCandidateRect.set(mOtherRect); + closest = focusable; + } + } + return closest; + } + + /** + * Is rect1 a better candidate than rect2 for a focus search in a particular + * direction from a source rect? This is the core routine that determines + * the order of focus searching. + * @param direction the direction (up, down, left, right) + * @param source The source we are searching from + * @param rect1 The candidate rectangle + * @param rect2 The current best candidate. + * @return Whether the candidate is the new best. + */ + boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) { + + // to be a better candidate, need to at least be a candidate in the first + // place :) + if (!isCandidate(source, rect1, direction)) { + return false; + } + + // we know that rect1 is a candidate.. if rect2 is not a candidate, + // rect1 is better + if (!isCandidate(source, rect2, direction)) { + return true; + } + + // if rect1 is better by beam, it wins + if (beamBeats(direction, source, rect1, rect2)) { + return true; + } + + // if rect2 is better, then rect1 cant' be :) + if (beamBeats(direction, source, rect2, rect1)) { + return false; + } + + // otherwise, do fudge-tastic comparison of the major and minor axis + return (getWeightedDistanceFor( + majorAxisDistance(direction, source, rect1), + minorAxisDistance(direction, source, rect1)) + < getWeightedDistanceFor( + majorAxisDistance(direction, source, rect2), + minorAxisDistance(direction, source, rect2))); + } + + /** + * One rectangle may be another candidate than another by virtue of being + * exclusively in the beam of the source rect. + * @return Whether rect1 is a better candidate than rect2 by virtue of it being in src's + * beam + */ + boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) { + final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1); + final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2); + + // if rect1 isn't exclusively in the src beam, it doesn't win + if (rect2InSrcBeam || !rect1InSrcBeam) { + return false; + } + + // we know rect1 is in the beam, and rect2 is not + + // if rect1 is to the direction of, and rect2 is not, rect1 wins. + // for example, for direction left, if rect1 is to the left of the source + // and rect2 is below, then we always prefer the in beam rect1, since rect2 + // could be reached by going down. + if (!isToDirectionOf(direction, source, rect2)) { + return true; + } + + // for horizontal directions, being exclusively in beam always wins + if ((direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) { + return true; + } + + // for vertical directions, beams only beat up to a point: + // now, as long as rect2 isn't completely closer, rect1 wins + // e.g for direction down, completely closer means for rect2's top + // edge to be closer to the source's top edge than rect1's bottom edge. + return (majorAxisDistance(direction, source, rect1) + < majorAxisDistanceToFarEdge(direction, source, rect2)); + } + + /** + * Fudge-factor opportunity: how to calculate distance given major and minor + * axis distances. Warning: this fudge factor is finely tuned, be sure to + * run all focus tests if you dare tweak it. + */ + int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) { + return 13 * majorAxisDistance * majorAxisDistance + + minorAxisDistance * minorAxisDistance; + } + + /** + * Is destRect a candidate for the next focus given the direction? This + * checks whether the dest is at least partially to the direction of (e.g left of) + * from source. + * + * Includes an edge case for an empty rect (which is used in some cases when + * searching from a point on the screen). + */ + boolean isCandidate(Rect srcRect, Rect destRect, int direction) { + switch (direction) { + case View.FOCUS_LEFT: + return (srcRect.right > destRect.right || srcRect.left >= destRect.right) + && srcRect.left > destRect.left; + case View.FOCUS_RIGHT: + return (srcRect.left < destRect.left || srcRect.right <= destRect.left) + && srcRect.right < destRect.right; + case View.FOCUS_UP: + return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom) + && srcRect.top > destRect.top; + case View.FOCUS_DOWN: + return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top) + && srcRect.bottom < destRect.bottom; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + + /** + * Do the "beams" w.r.t the given direcition's axos of rect1 and rect2 overlap? + * @param direction the direction (up, down, left, right) + * @param rect1 The first rectangle + * @param rect2 The second rectangle + * @return whether the beams overlap + */ + boolean beamsOverlap(int direction, Rect rect1, Rect rect2) { + switch (direction) { + case View.FOCUS_LEFT: + case View.FOCUS_RIGHT: + return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom); + case View.FOCUS_UP: + case View.FOCUS_DOWN: + return (rect2.right >= rect1.left) && (rect2.left <= rect1.right); + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * e.g for left, is 'to left of' + */ + boolean isToDirectionOf(int direction, Rect src, Rect dest) { + switch (direction) { + case View.FOCUS_LEFT: + return src.left >= dest.right; + case View.FOCUS_RIGHT: + return src.right <= dest.left; + case View.FOCUS_UP: + return src.top >= dest.bottom; + case View.FOCUS_DOWN: + return src.bottom <= dest.top; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * @return The distance from the edge furthest in the given direction + * of source to the edge nearest in the given direction of dest. If the + * dest is not in the direction from source, return 0. + */ + static int majorAxisDistance(int direction, Rect source, Rect dest) { + return Math.max(0, majorAxisDistanceRaw(direction, source, dest)); + } + + static int majorAxisDistanceRaw(int direction, Rect source, Rect dest) { + switch (direction) { + case View.FOCUS_LEFT: + return source.left - dest.right; + case View.FOCUS_RIGHT: + return dest.left - source.right; + case View.FOCUS_UP: + return source.top - dest.bottom; + case View.FOCUS_DOWN: + return dest.top - source.bottom; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * @return The distance along the major axis w.r.t the direction from the + * edge of source to the far edge of dest. If the + * dest is not in the direction from source, return 1 (to break ties with + * {@link #majorAxisDistance}). + */ + static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) { + return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest)); + } + + static int majorAxisDistanceToFarEdgeRaw(int direction, Rect source, Rect dest) { + switch (direction) { + case View.FOCUS_LEFT: + return source.left - dest.left; + case View.FOCUS_RIGHT: + return dest.right - source.right; + case View.FOCUS_UP: + return source.top - dest.top; + case View.FOCUS_DOWN: + return dest.bottom - source.bottom; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * Find the distance on the minor axis w.r.t the direction to the nearest + * edge of the destination rectange. + * @param direction the direction (up, down, left, right) + * @param source The source rect. + * @param dest The destination rect. + * @return The distance. + */ + static int minorAxisDistance(int direction, Rect source, Rect dest) { + switch (direction) { + case View.FOCUS_LEFT: + case View.FOCUS_RIGHT: + // the distance between the center verticals + return Math.abs( + ((source.top + source.height() / 2) - + ((dest.top + dest.height() / 2)))); + case View.FOCUS_UP: + case View.FOCUS_DOWN: + // the distance between the center horizontals + return Math.abs( + ((source.left + source.width() / 2) - + ((dest.left + dest.width() / 2)))); + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } + + /** + * Find the nearest touchable view to the specified view. + * + * @param root The root of the tree in which to search + * @param x X coordinate from which to start the search + * @param y Y coordinate from which to start the search + * @param direction Direction to look + * @param deltas Offset from the <x, y> to the edge of the nearest view. Note that this array + * may already be populated with values. + * @return The nearest touchable view, or null if none exists. + */ + public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) { + ArrayList<View> touchables = root.getTouchables(); + int minDistance = Integer.MAX_VALUE; + View closest = null; + + int numTouchables = touchables.size(); + + int edgeSlop = ViewConfiguration.getEdgeSlop(); + + Rect closestBounds = new Rect(); + Rect touchableBounds = mOtherRect; + + for (int i = 0; i < numTouchables; i++) { + View touchable = touchables.get(i); + + // get visible bounds of other view in same coordinate system + touchable.getDrawingRect(touchableBounds); + + root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true); + + if (!isTouchCandidate(x, y, touchableBounds, direction)) { + continue; + } + + int distance = Integer.MAX_VALUE; + + switch (direction) { + case View.FOCUS_LEFT: + distance = x - touchableBounds.right + 1; + break; + case View.FOCUS_RIGHT: + distance = touchableBounds.left; + break; + case View.FOCUS_UP: + distance = y - touchableBounds.bottom + 1; + break; + case View.FOCUS_DOWN: + distance = touchableBounds.top; + break; + } + + if (distance < edgeSlop) { + // Give preference to innermost views + if (closest == null || + closestBounds.contains(touchableBounds) || + (!touchableBounds.contains(closestBounds) && distance < minDistance)) { + minDistance = distance; + closest = touchable; + closestBounds.set(touchableBounds); + switch (direction) { + case View.FOCUS_LEFT: + deltas[0] = -distance; + break; + case View.FOCUS_RIGHT: + deltas[0] = distance; + break; + case View.FOCUS_UP: + deltas[1] = -distance; + break; + case View.FOCUS_DOWN: + deltas[1] = distance; + break; + } + } + } + } + return closest; + } + + + /** + * Is destRect a candidate for the next touch given the direction? + */ + private boolean isTouchCandidate(int x, int y, Rect destRect, int direction) { + switch (direction) { + case View.FOCUS_LEFT: + return destRect.left <= x && destRect.top <= y && y <= destRect.bottom; + case View.FOCUS_RIGHT: + return destRect.left >= x && destRect.top <= y && y <= destRect.bottom; + case View.FOCUS_UP: + return destRect.top <= y && destRect.left <= x && x <= destRect.right; + case View.FOCUS_DOWN: + return destRect.top >= y && destRect.left <= x && x <= destRect.right; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}."); + } +} diff --git a/core/java/android/view/FocusFinderHelper.java b/core/java/android/view/FocusFinderHelper.java new file mode 100644 index 0000000..69dc056 --- /dev/null +++ b/core/java/android/view/FocusFinderHelper.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008 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.view; + +import android.graphics.Rect; + +/** + * A helper class that allows unit tests to access FocusFinder methods. + * @hide + */ +public class FocusFinderHelper { + + private FocusFinder mFocusFinder; + + /** + * Wrap the FocusFinder object + */ + public FocusFinderHelper(FocusFinder focusFinder) { + mFocusFinder = focusFinder; + } + + public boolean isBetterCandidate(int direction, Rect source, Rect rect1, Rect rect2) { + return mFocusFinder.isBetterCandidate(direction, source, rect1, rect2); + } + + public boolean beamBeats(int direction, Rect source, Rect rect1, Rect rect2) { + return mFocusFinder.beamBeats(direction, source, rect1, rect2); + } + + public boolean isCandidate(Rect srcRect, Rect destRect, int direction) { + return mFocusFinder.isCandidate(srcRect, destRect, direction); + } + + public boolean beamsOverlap(int direction, Rect rect1, Rect rect2) { + return mFocusFinder.beamsOverlap(direction, rect1, rect2); + } + + public static int majorAxisDistance(int direction, Rect source, Rect dest) { + return FocusFinder.majorAxisDistance(direction, source, dest); + } + + public static int majorAxisDistanceToFarEdge(int direction, Rect source, Rect dest) { + return FocusFinder.majorAxisDistanceToFarEdge(direction, source, dest); + } +} diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java new file mode 100644 index 0000000..fc9af05 --- /dev/null +++ b/core/java/android/view/GestureDetector.java @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2008 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.view; + +import android.os.Handler; +import android.os.Message; + +/** + * Detects various gestures and events using the supplied {@link MotionEvent}s. + * The {@link OnGestureListener} callback will notify users when a particular + * motion event has occurred. This class should only be used with {@link MotionEvent}s + * reported via touch (don't use for trackball events). + * + * To use this class: + * <ul> + * <li>Create an instance of the {@code GestureDetector} for your {@link View} + * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call + * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback + * will be executed when the events occur. + * </ul> + */ +public class GestureDetector { + + /** + * The listener that is used to notify when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnGestureListener}. + */ + public interface OnGestureListener { + + /** + * Notified when a tap occurs with the down {@link MotionEvent} + * that triggered it. This will be triggered immediately for + * every down event. All other events should be preceded by this. + * + * @param e The down motion event. + */ + boolean onDown(MotionEvent e); + + /** + * The user has performed a down {@link MotionEvent} and not performed + * a move or up yet. This event is commonly used to provide visual + * feedback to the user to let them know that their action has been + * recognized i.e. highlight an element. + * + * @param e The down motion event + */ + void onShowPress(MotionEvent e); + + /** + * Notified when a tap occurs with the up {@link MotionEvent} + * that triggered it. + * + * @param e The up motion event that completed the first tap + * @return true if the event is consumed, else false + */ + boolean onSingleTapUp(MotionEvent e); + + /** + * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the + * current move {@link MotionEvent}. The distance in x and y is also supplied for + * convenience. + * + * @param e1 The first down motion event that started the scrolling. + * @param e2 The move motion event that triggered the current onScroll. + * @param distanceX The distance along the X axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @param distanceY The distance along the Y axis that has been scrolled since the last + * call to onScroll. This is NOT the distance between {@code e1} + * and {@code e2}. + * @return true if the event is consumed, else false + */ + boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); + + /** + * Notified when a long press occurs with the initial on down {@link MotionEvent} + * that trigged it. + * + * @param e The initial on down motion event that started the longpress. + */ + void onLongPress(MotionEvent e); + + /** + * Notified of a fling event when it occurs with the initial on down {@link MotionEvent} + * and the matching up {@link MotionEvent}. The calculated velocity is supplied along + * the x and y axis in pixels per second. + * + * @param e1 The first down motion event that started the fling. + * @param e2 The move motion event that triggered the current onFling. + * @param velocityX The velocity of this fling measured in pixels per second + * along the x axis. + * @param velocityY The velocity of this fling measured in pixels per second + * along the y axis. + * @return true if the event is consumed, else false + */ + boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); + } + + /** + * A convenience class to extend when you only want to listen for a + * subset of all the gestures. This implements all methods in the + * {@link OnGestureListener} but does nothing and return {@code false} + * for all applicable methods. + */ + public static class SimpleOnGestureListener implements OnGestureListener { + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + public void onLongPress(MotionEvent e) { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + return false; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + return false; + } + + public void onShowPress(MotionEvent e) { + } + + public boolean onDown(MotionEvent e) { + return false; + } + } + + private static final int TOUCH_SLOP_SQUARE = ViewConfiguration.getTouchSlop() + * ViewConfiguration.getTouchSlop(); + + // constants for Message.what used by GestureHandler below + private static final int SHOW_PRESS = 1; + private static final int LONG_PRESS = 2; + + private final Handler mHandler; + private final OnGestureListener mListener; + + private boolean mInLongPress; + private boolean mAlwaysInTapRegion; + + private MotionEvent mCurrentDownEvent; + private MotionEvent mCurrentUpEvent; + + private float mLastMotionY; + private float mLastMotionX; + + private boolean mIsLongpressEnabled; + + /** + * Determines speed during touch scrolling + */ + private VelocityTracker mVelocityTracker; + + private class GestureHandler extends Handler { + GestureHandler() { + super(); + } + + GestureHandler(Handler handler) { + super(handler.getLooper()); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SHOW_PRESS: + mListener.onShowPress(mCurrentDownEvent); + break; + + case LONG_PRESS: + dispatchLongPress(); + break; + + default: + throw new RuntimeException("Unknown message " + msg); //never + } + } + } + + /** + * Creates a GestureDetector with the supplied listener. + * This variant of the constructor should be used from a non-UI thread + * (as it allows specifying the Handler). + * + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use, this must + * not be null. + * + * @throws NullPointerException if either {@code listener} or + * {@code handler} is null. + */ + public GestureDetector(OnGestureListener listener, Handler handler) { + mHandler = new GestureHandler(handler); + mListener = listener; + init(); + } + + /** + * Creates a GestureDetector with the supplied listener. + * You may only use this constructor from a UI thread (this is the usual situation). + * @see android.os.Handler#Handler() + * + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @throws NullPointerException if {@code listener} is null. + */ + public GestureDetector(OnGestureListener listener) { + mHandler = new GestureHandler(); + mListener = listener; + init(); + } + + private void init() { + if (mListener == null) { + throw new NullPointerException("OnGestureListener must not be null"); + } + mIsLongpressEnabled = true; + } + + /** + * Set whether longpress is enabled, if this is enabled when a user + * presses and holds down you get a longpress event and nothing further. + * If it's disabled the user can press and hold down and then later + * moved their finger and you will get scroll events. By default + * longpress is enabled. + * + * @param isLongpressEnabled whether longpress should be enabled. + */ + public void setIsLongpressEnabled(boolean isLongpressEnabled) { + mIsLongpressEnabled = isLongpressEnabled; + } + + /** + * @return true if longpress is enabled, else false. + */ + public boolean isLongpressEnabled() { + return mIsLongpressEnabled; + } + + /** + * Analyzes the given motion event and if applicable triggers the + * appropriate callbacks on the {@link OnGestureListener} supplied. + * + * @param ev The current motion event. + * @return true if the {@link OnGestureListener} consumed the event, + * else false. + */ + public boolean onTouchEvent(MotionEvent ev) { + final long tapTime = ViewConfiguration.getTapTimeout(); + final long longpressTime = ViewConfiguration.getLongPressTimeout(); + final int touchSlop = ViewConfiguration.getTouchSlop(); + final int action = ev.getAction(); + final float y = ev.getY(); + final float x = ev.getX(); + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + boolean handled = false; + + switch (action) { + case MotionEvent.ACTION_DOWN: + mLastMotionX = x; + mLastMotionY = y; + mCurrentDownEvent = MotionEvent.obtain(ev); + mAlwaysInTapRegion = true; + mInLongPress = false; + + if (mIsLongpressEnabled) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + + tapTime + longpressTime); + } + mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + tapTime); + handled = mListener.onDown(ev); + break; + + case MotionEvent.ACTION_MOVE: + if (mInLongPress) { + break; + } + final float scrollX = mLastMotionX - x; + final float scrollY = mLastMotionY - y; + if (mAlwaysInTapRegion) { + final int deltaX = (int) (x - mCurrentDownEvent.getX()); + final int deltaY = (int) (y - mCurrentDownEvent.getY()); + int distance = (deltaX * deltaX) + (deltaY * deltaY); + if (distance > TOUCH_SLOP_SQUARE) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastMotionX = x; + mLastMotionY = y; + mAlwaysInTapRegion = false; + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + } + } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastMotionX = x; + mLastMotionY = y; + } + break; + + case MotionEvent.ACTION_UP: + mCurrentUpEvent = MotionEvent.obtain(ev); + if (mInLongPress) { + mInLongPress = false; + break; + } + if (mAlwaysInTapRegion) { + handled = mListener.onSingleTapUp(ev); + } else { + + // A fling must travel the minimum tap distance + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000); + final float velocityY = velocityTracker.getYVelocity(); + final float velocityX = velocityTracker.getXVelocity(); + + if ((Math.abs(velocityY) > ViewConfiguration.getMinimumFlingVelocity()) + || (Math.abs(velocityX) > ViewConfiguration.getMinimumFlingVelocity())){ + handled = mListener.onFling(mCurrentDownEvent, mCurrentUpEvent, velocityX, velocityY); + } + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + break; + case MotionEvent.ACTION_CANCEL: + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + mVelocityTracker.recycle(); + mVelocityTracker = null; + if (mInLongPress) { + mInLongPress = false; + break; + } + } + return handled; + } + + private void dispatchLongPress() { + mInLongPress = true; + mListener.onLongPress(mCurrentDownEvent); + } +} diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java new file mode 100644 index 0000000..ff9ab18 --- /dev/null +++ b/core/java/android/view/Gravity.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2006 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.view; +import android.graphics.Rect; + +/** + * Standard constants and tools for placing an object within a potentially + * larger container. + */ +public class Gravity +{ + /** Contstant indicating that no gravity has been set **/ + public static final int NO_GRAVITY = 0x0000; + + /** Raw bit indicating the gravity for an axis has been specified. */ + public static final int AXIS_SPECIFIED = 0x0001; + + /** Raw bit controlling how the left/top edge is placed. */ + public static final int AXIS_PULL_BEFORE = 0x0002; + /** Raw bit controlling how the right/bottom edge is placed. */ + public static final int AXIS_PULL_AFTER = 0x0004; + + /** Bits defining the horizontal axis. */ + public static final int AXIS_X_SHIFT = 0; + /** Bits defining the vertical axis. */ + public static final int AXIS_Y_SHIFT = 4; + + /** Push object to the top of its container, not changing its size. */ + public static final int TOP = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_Y_SHIFT; + /** Push object to the bottom of its container, not changing its size. */ + public static final int BOTTOM = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_Y_SHIFT; + /** Push object to the left of its container, not changing its size. */ + public static final int LEFT = (AXIS_PULL_BEFORE|AXIS_SPECIFIED)<<AXIS_X_SHIFT; + /** Push object to the right of its container, not changing its size. */ + public static final int RIGHT = (AXIS_PULL_AFTER|AXIS_SPECIFIED)<<AXIS_X_SHIFT; + + /** Place object in the vertical center of its container, not changing its + * size. */ + public static final int CENTER_VERTICAL = AXIS_SPECIFIED<<AXIS_Y_SHIFT; + /** Grow the vertical size of the object if needed so it completely fills + * its container. */ + public static final int FILL_VERTICAL = TOP|BOTTOM; + + /** Place object in the horizontal center of its container, not changing its + * size. */ + public static final int CENTER_HORIZONTAL = AXIS_SPECIFIED<<AXIS_X_SHIFT; + /** Grow the horizontal size of the object if needed so it completely fills + * its container. */ + public static final int FILL_HORIZONTAL = LEFT|RIGHT; + + /** Place the object in the center of its container in both the vertical + * and horizontal axis, not changing its size. */ + public static final int CENTER = CENTER_VERTICAL|CENTER_HORIZONTAL; + + /** Grow the horizontal and vertical size of the obejct if needed so it + * completely fills its container. */ + public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL; + + /** + * Binary mask to get the horizontal gravity of a gravity. + */ + public static final int HORIZONTAL_GRAVITY_MASK = (AXIS_SPECIFIED | + AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_X_SHIFT; + /** + * Binary mask to get the vertical gravity of a gravity. + */ + public static final int VERTICAL_GRAVITY_MASK = (AXIS_SPECIFIED | + AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_Y_SHIFT; + + /** + * Apply a gravity constant to an object. + * + * @param gravity The desired placement of the object, as defined by the + * constants in this class. + * @param w The horizontal size of the object. + * @param h The vertical size of the object. + * @param container The frame of the containing space, in which the object + * will be placed. Should be large enough to contain the + * width and height of the object. + * @param outRect Receives the computed frame of the object in its + * container. + */ + public static void apply(int gravity, int w, int h, Rect container, + Rect outRect) { + apply(gravity, w, h, container, 0, 0, outRect); + } + + /** + * Apply a gravity constant to an object. + * + * @param gravity The desired placement of the object, as defined by the + * constants in this class. + * @param w The horizontal size of the object. + * @param h The vertical size of the object. + * @param container The frame of the containing space, in which the object + * will be placed. Should be large enough to contain the + * width and height of the object. + * @param xAdj Offset to apply to the X axis. If gravity is LEFT this + * pushes it to the right; if gravity is RIGHT it pushes it to + * the left; if gravity is CENTER_HORIZONTAL it pushes it to the + * right or left; otherwise it is ignored. + * @param yAdj Offset to apply to the Y axis. If gravity is TOP this pushes + * it down; if gravity is BOTTOM it pushes it up; if gravity is + * CENTER_VERTICAL it pushes it down or up; otherwise it is + * ignored. + * @param outRect Receives the computed frame of the object in its + * container. + */ + public static void apply(int gravity, int w, int h, Rect container, + int xAdj, int yAdj, Rect outRect) { + if ((gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) + == ((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) { + outRect.left = container.left; + outRect.right = container.right; + } else { + outRect.left = applyMovement( + gravity>>AXIS_X_SHIFT, w, container.left, container.right, xAdj); + outRect.right = outRect.left + w; + } + + if ((gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) + == ((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) { + outRect.top = container.top; + outRect.bottom = container.bottom; + } else { + outRect.top = applyMovement( + gravity>>AXIS_Y_SHIFT, h, container.top, container.bottom, yAdj); + outRect.bottom = outRect.top + h; + } + } + + /** + * <p>Indicate whether the supplied gravity has a vertical pull.</p> + * + * @param gravity the gravity to check for vertical pull + * @return true if the supplied gravity has a vertical pull + */ + public static boolean isVertical(int gravity) { + return gravity > 0 && (gravity & VERTICAL_GRAVITY_MASK) != 0; + } + + /** + * <p>Indicate whether the supplied gravity has an horizontal pull.</p> + * + * @param gravity the gravity to check for horizontal pull + * @return true if the supplied gravity has an horizontal pull + */ + public static boolean isHorizontal(int gravity) { + return gravity > 0 && (gravity & HORIZONTAL_GRAVITY_MASK) != 0; + } + + private static int applyMovement(int mode, int size, + int start, int end, int adj) { + if ((mode & AXIS_PULL_BEFORE) != 0) { + return start + adj; + } + + if ((mode & AXIS_PULL_AFTER) != 0) { + return end - size - adj; + } + + return start + ((end - start - size)/2) + adj; + } +} + diff --git a/core/java/android/view/IApplicationToken.aidl b/core/java/android/view/IApplicationToken.aidl new file mode 100644 index 0000000..6bff5b3 --- /dev/null +++ b/core/java/android/view/IApplicationToken.aidl @@ -0,0 +1,28 @@ +/* //device/java/android/android/view/IApplicationToken.aidl +** +** Copyright 2007, 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.view; + +/** {@hide} */ +interface IApplicationToken +{ + void windowsVisible(); + void windowsGone(); + boolean keyDispatchingTimedOut(); + long getKeyDispatchingTimeout(); +} + diff --git a/core/java/android/view/IOnKeyguardExitResult.aidl b/core/java/android/view/IOnKeyguardExitResult.aidl new file mode 100644 index 0000000..47d5220 --- /dev/null +++ b/core/java/android/view/IOnKeyguardExitResult.aidl @@ -0,0 +1,25 @@ +/* //device/java/android/android/hardware/ISensorListener.aidl +** +** Copyright 2008, 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.view; + +/** @hide */ +oneway interface IOnKeyguardExitResult { + + void onKeyguardExitResult(boolean success); + +} diff --git a/core/java/android/view/IRotationWatcher.aidl b/core/java/android/view/IRotationWatcher.aidl new file mode 100644 index 0000000..2c83642 --- /dev/null +++ b/core/java/android/view/IRotationWatcher.aidl @@ -0,0 +1,25 @@ +/* //device/java/android/android/hardware/ISensorListener.aidl +** +** Copyright 2008, 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.view; + +/** + * {@hide} + */ +oneway interface IRotationWatcher { + void onRotationChanged(int rotation); +} diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl new file mode 100644 index 0000000..b4a3067 --- /dev/null +++ b/core/java/android/view/IWindow.aidl @@ -0,0 +1,57 @@ +/* //device/java/android/android/view/IWindow.aidl +** +** Copyright 2007, 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.view; + +import android.view.KeyEvent; +import android.view.MotionEvent; + +import android.os.ParcelFileDescriptor; + +/** + * API back to a client window that the Window Manager uses to inform it of + * interesting things happening. + * + * {@hide} + */ +oneway interface IWindow { + /** + * ===== NOTICE ===== + * The first method must remain the first method. Scripts + * and tools rely on their transaction number to work properly. + */ + + /** + * Invoked by the view server to tell a window to execute the specified + * command. Any response from the receiver must be sent through the + * specified file descriptor. + */ + void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor); + + void resized(int w, int h, boolean reportDraw); + void dispatchKey(in KeyEvent event); + void dispatchPointer(in MotionEvent event, long eventTime); + void dispatchTrackball(in MotionEvent event, long eventTime); + void dispatchAppVisibility(boolean visible); + void dispatchGetNewSurface(); + + /** + * Tell the window that it is either gaining or losing focus. Keep it up + * to date on the current state showing navigational focus (touch mode) too. + */ + void windowFocusChanged(boolean hasFocus, boolean inTouchMode); +} diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl new file mode 100644 index 0000000..e6d52e2 --- /dev/null +++ b/core/java/android/view/IWindowManager.aidl @@ -0,0 +1,125 @@ +/* //device/java/android/android/view/IWindowManager.aidl +** +** Copyright 2006, 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.view; + +import android.content.res.Configuration; +import android.view.IApplicationToken; +import android.view.IOnKeyguardExitResult; +import android.view.IRotationWatcher; +import android.view.IWindowSession; +import android.view.KeyEvent; +import android.view.MotionEvent; + +/** + * System private interface to the window manager. + * + * {@hide} + */ +interface IWindowManager +{ + /** + * ===== NOTICE ===== + * The first three methods must remain the first three methods. Scripts + * and tools rely on their transaction number to work properly. + */ + // This is used for debugging + boolean startViewServer(int port); // Transaction #1 + boolean stopViewServer(); // Transaction #2 + boolean isViewServerRunning(); // Transaction #3 + + IWindowSession openSession(IBinder token); + + // These can only be called when injecting events to your own window, + // or by holding the INJECT_EVENTS permission. + boolean injectKeyEvent(in KeyEvent ev, boolean sync); + boolean injectPointerEvent(in MotionEvent ev, boolean sync); + boolean injectTrackballEvent(in MotionEvent ev, boolean sync); + + // These can only be called when holding the MANAGE_APP_TOKENS permission. + void pauseKeyDispatching(IBinder token); + void resumeKeyDispatching(IBinder token); + void setEventDispatching(boolean enabled); + void addAppToken(int addPos, IApplicationToken token, + int groupId, int requestedOrientation, boolean fullscreen); + void setAppGroupId(IBinder token, int groupId); + Configuration updateOrientationFromAppTokens(IBinder freezeThisOneIfNeeded); + void setAppOrientation(IApplicationToken token, int requestedOrientation); + int getAppOrientation(IApplicationToken token); + void setFocusedApp(IBinder token, boolean moveFocusNow); + void prepareAppTransition(int transit); + void executeAppTransition(); + void setAppStartingWindow(IBinder token, String pkg, int theme, + CharSequence nonLocalizedLabel, int labelRes, + int icon, IBinder transferFrom, boolean createIfNeeded); + void setAppWillBeHidden(IBinder token); + void setAppVisibility(IBinder token, boolean visible); + void startAppFreezingScreen(IBinder token, int configChanges); + void stopAppFreezingScreen(IBinder token, boolean force); + void removeAppToken(IBinder token); + void moveAppToken(int index, IBinder token); + void moveAppTokensToTop(in List<IBinder> tokens); + void moveAppTokensToBottom(in List<IBinder> tokens); + + // these require DISABLE_KEYGUARD permission + void disableKeyguard(IBinder token, String tag); + void reenableKeyguard(IBinder token); + void exitKeyguardSecurely(IOnKeyguardExitResult callback); + boolean inKeyguardRestrictedInputMode(); + + + // These can only be called with the SET_ANIMATON_SCALE permission. + float getAnimationScale(int which); + float[] getAnimationScales(); + void setAnimationScale(int which, float scale); + void setAnimationScales(in float[] scales); + + // These require the READ_INPUT_STATE permission. + int getSwitchState(int sw); + int getSwitchStateForDevice(int devid, int sw); + int getScancodeState(int sw); + int getScancodeStateForDevice(int devid, int sw); + int getKeycodeState(int sw); + int getKeycodeStateForDevice(int devid, int sw); + + // For testing + void setInTouchMode(boolean showFocus); + + // These can only be called with the SET_ORIENTATION permission. + /** + * Change the current screen rotation, constants as per + * {@link android.view.Surface}. + * @param rotation the intended rotation. + * @param alwaysSendConfiguration Flag to force a new configuration to + * be evaluated. This can be used when there are other parameters in + * configuration that are changing. + * {@link android.view.Surface}. + */ + void setRotation(int rotation, boolean alwaysSendConfiguration); + + /** + * Retrieve the current screen orientation, constants as per + * {@link android.view.Surface}. + */ + int getRotation(); + + /** + * Watch the rotation of the screen. Returns the current rotation, + * calls back when it changes. + */ + int watchRotation(IRotationWatcher watcher); +} diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl new file mode 100644 index 0000000..c2c0b97 --- /dev/null +++ b/core/java/android/view/IWindowSession.aidl @@ -0,0 +1,73 @@ +/* //device/java/android/android/view/IWindowSession.aidl +** +** Copyright 2006, 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.view; + +import android.graphics.Rect; +import android.graphics.Region; +import android.view.IWindow; +import android.view.MotionEvent; +import android.view.WindowManager; +import android.view.Surface; + +/** + * System private per-application interface to the window manager. + * + * {@hide} + */ +interface IWindowSession { + int add(IWindow window, in WindowManager.LayoutParams attrs, + in int viewVisibility, out Rect outCoveredInsets); + void remove(IWindow window); + + /** + * Change the parameters of a window. You supply the + * new parameters, it returns the new frame of the window on screen (the + * position should be ignored) and surface of the window. The surface + * will be invalid if the window is currently hidden, else you can use it + * to draw the window's contents. + * + * @param window The window being modified. + * @param attrs If non-null, new attributes to apply to the window. + * @param requestedWidth The width the window wants to be. + * @param requestedHeight The height the window wants to be. + * @param viewVisibility Window root view's visibility. + * @param outFrame Object in which is placed the new position/size on + * screen. + * @param outCoveredInsets Object in which is placed the insets for the areas covered by + * system windows (e.g. status bar) + * @param outSurface Object in which is placed the new display surface. + * + * @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS}, + * {@link WindowManagerImpl#RELAYOUT_FIRST_TIME}. + */ + int relayout(IWindow window, in WindowManager.LayoutParams attrs, + int requestedWidth, int requestedHeight, int viewVisibility, + out Rect outFrame, out Rect outCoveredInsets, out Surface outSurface); + + void finishDrawing(IWindow window); + + void finishKey(IWindow window); + MotionEvent getPendingPointerMove(IWindow window); + MotionEvent getPendingTrackballMove(IWindow window); + + void setTransparentRegion(IWindow window, in Region region); + + void setInTouchMode(boolean showFocus); + boolean getInTouchMode(); +} + diff --git a/core/java/android/view/InflateException.java b/core/java/android/view/InflateException.java new file mode 100644 index 0000000..7b39d33 --- /dev/null +++ b/core/java/android/view/InflateException.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 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.view; + +/** + * This exception is thrown by an inflater on error conditions. + */ +public class InflateException extends RuntimeException { + + public InflateException() { + super(); + } + + public InflateException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public InflateException(String detailMessage) { + super(detailMessage); + } + + public InflateException(Throwable throwable) { + super(throwable); + } + +} diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java new file mode 100644 index 0000000..0347d50 --- /dev/null +++ b/core/java/android/view/KeyCharacterMap.java @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2007 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.view; + +import android.text.method.MetaKeyKeyListener; +import android.util.SparseIntArray; +import android.os.SystemClock; +import android.util.SparseArray; + +import java.lang.Character; +import java.lang.ref.WeakReference; + +public class KeyCharacterMap +{ + /** + * The id of the device's primary built in keyboard is always 0. + */ + public static final int BUILT_IN_KEYBOARD = 0; + + /** A numeric (12-key) keyboard. */ + public static final int NUMERIC = 1; + + /** A keyboard with all the letters, but with more than one letter + * per key. */ + public static final int PREDICTIVE = 2; + + /** A keyboard with all the letters, and maybe some numbers. */ + public static final int ALPHA = 3; + + /** + * This private-use character is used to trigger Unicode character + * input by hex digits. + */ + public static final char HEX_INPUT = '\uEF00'; + + /** + * This private-use character is used to bring up a character picker for + * miscellaneous symbols. + */ + public static final char PICKER_DIALOG_INPUT = '\uEF01'; + + private static Object sLock = new Object(); + private static SparseArray<WeakReference<KeyCharacterMap>> sInstances + = new SparseArray<WeakReference<KeyCharacterMap>>(); + + public static KeyCharacterMap load(int keyboard) + { + synchronized (sLock) { + KeyCharacterMap result; + WeakReference<KeyCharacterMap> ref = sInstances.get(keyboard); + if (ref != null) { + result = ref.get(); + if (result != null) { + return result; + } + } + result = new KeyCharacterMap(keyboard); + sInstances.put(keyboard, new WeakReference<KeyCharacterMap>(result)); + return result; + } + } + + private KeyCharacterMap(int keyboardDevice) + { + mKeyboardDevice = keyboardDevice; + mPointer = ctor_native(keyboardDevice); + } + + /** + * <p> + * Returns the Unicode character that the specified key would produce + * when the specified meta bits (see {@link MetaKeyKeyListener}) + * were active. + * </p><p> + * Returns 0 if the key is not one that is used to type Unicode + * characters. + * </p><p> + * If the return value has bit {@link #COMBINING_ACCENT} set, the + * key is a "dead key" that should be combined with another to + * actually produce a character -- see {@link #getDeadChar} -- + * after masking with {@link #COMBINING_ACCENT_MASK}. + * </p> + */ + public int get(int keyCode, int meta) + { + if ((meta & MetaKeyKeyListener.META_CAP_LOCKED) != 0) { + meta |= KeyEvent.META_SHIFT_ON; + } + if ((meta & MetaKeyKeyListener.META_ALT_LOCKED) != 0) { + meta |= KeyEvent.META_ALT_ON; + } + + // Ignore caps lock on keys where alt and shift have the same effect. + if ((meta & MetaKeyKeyListener.META_CAP_LOCKED) != 0) { + if (get_native(mPointer, keyCode, KeyEvent.META_SHIFT_ON) == + get_native(mPointer, keyCode, KeyEvent.META_ALT_ON)) { + meta &= ~KeyEvent.META_SHIFT_ON; + } + } + + int ret = get_native(mPointer, keyCode, meta); + int map = COMBINING.get(ret); + + if (map != 0) { + return map; + } else { + return ret; + } + } + + /** + * Gets the number or symbol associated with the key. The character value + * is returned, not the numeric value. If the key is not a number, but is + * a symbol, the symbol is retuned. + */ + public char getNumber(int keyCode) + { + return getNumber_native(mPointer, keyCode); + } + + /** + * The same as {@link #getMatch(int,char[],int) getMatch(keyCode, chars, 0)}. + */ + public char getMatch(int keyCode, char[] chars) + { + return getMatch(keyCode, chars, 0); + } + + /** + * If one of the chars in the array can be generated by keyCode, + * return the char; otherwise return '\0'. + * @param keyCode the key code to look at + * @param chars the characters to try to find + * @param modifiers the modifier bits to prefer. If any of these bits + * are set, if there are multiple choices, that could + * work, the one for this modifier will be set. + */ + public char getMatch(int keyCode, char[] chars, int modifiers) + { + if (chars == null) { + // catch it here instead of in native + throw new NullPointerException(); + } + return getMatch_native(mPointer, keyCode, chars, modifiers); + } + + /** + * Get the primary character for this key. In other words, the label + * that is physically printed on it. + */ + public char getDisplayLabel(int keyCode) + { + return getDisplayLabel_native(mPointer, keyCode); + } + + /** + * Get the character that is produced by putting accent on the character + * c. + * For example, getDeadChar('`', 'e') returns è. + */ + public static int getDeadChar(int accent, int c) + { + return DEAD.get((accent << 16) | c); + } + + public static class KeyData { + public static final int META_LENGTH = 4; + + /** + * The display label (see {@link #getDisplayLabel}). + */ + public char displayLabel; + /** + * The "number" value (see {@link #getNumber}). + */ + public char number; + /** + * The character that will be generated in various meta states + * (the same ones used for {@link #get} and defined as + * {@link KeyEvent#META_SHIFT_ON} and {@link KeyEvent#META_ALT_ON}). + * <table> + * <tr><th>Index</th><th align="left">Value</th></tr> + * <tr><td>0</td><td>no modifiers</td></tr> + * <tr><td>1</td><td>caps</td></tr> + * <tr><td>2</td><td>alt</td></tr> + * <tr><td>3</td><td>caps + alt</td></tr> + * </table> + */ + public char[] meta = new char[META_LENGTH]; + } + + /** + * Get the characters conversion data for a given keyCode. + * + * @param keyCode the keyCode to look for + * @param results a {@link KeyData} that will be filled with the results. + * + * @return whether the key was mapped or not. If the key was not mapped, + * results is not modified. + */ + public boolean getKeyData(int keyCode, KeyData results) + { + if (results.meta.length >= KeyData.META_LENGTH) { + return getKeyData_native(mPointer, keyCode, results); + } else { + throw new IndexOutOfBoundsException("results.meta.length must be >= " + + KeyData.META_LENGTH); + } + } + + /** + * Get an array of KeyEvent objects that if put into the input stream + * could plausibly generate the provided sequence of characters. It is + * not guaranteed that the sequence is the only way to generate these + * events or that it is optimal. + * + * @return an array of KeyEvent objects, or null if the given char array + * can not be generated using the current key character map. + */ + public KeyEvent[] getEvents(char[] chars) + { + if (chars == null) { + throw new NullPointerException(); + } + + long[] keys = getEvents_native(mPointer, chars); + if (keys == null) { + return null; + } + + // how big should the array be + int len = keys.length*2; + int N = keys.length; + for (int i=0; i<N; i++) { + int mods = (int)(keys[i] >> 32); + if ((mods & KeyEvent.META_ALT_ON) != 0) { + len += 2; + } + if ((mods & KeyEvent.META_SHIFT_ON) != 0) { + len += 2; + } + if ((mods & KeyEvent.META_SYM_ON) != 0) { + len += 2; + } + } + + // create the events + KeyEvent[] rv = new KeyEvent[len]; + int index = 0; + long now = SystemClock.uptimeMillis(); + int device = mKeyboardDevice; + for (int i=0; i<N; i++) { + int mods = (int)(keys[i] >> 32); + int meta = 0; + + if ((mods & KeyEvent.META_ALT_ON) != 0) { + meta |= KeyEvent.META_ALT_ON; + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_ALT_LEFT, 0, meta, device, 0); + index++; + } + if ((mods & KeyEvent.META_SHIFT_ON) != 0) { + meta |= KeyEvent.META_SHIFT_ON; + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_SHIFT_LEFT, 0, meta, device, 0); + index++; + } + if ((mods & KeyEvent.META_SYM_ON) != 0) { + meta |= KeyEvent.META_SYM_ON; + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_SYM, 0, meta, device, 0); + index++; + } + + int key = (int)(keys[i]); + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, + key, 0, meta, device, 0); + index++; + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP, + key, 0, meta, device, 0); + index++; + + if ((mods & KeyEvent.META_ALT_ON) != 0) { + meta &= ~KeyEvent.META_ALT_ON; + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_ALT_LEFT, 0, meta, device, 0); + index++; + } + if ((mods & KeyEvent.META_SHIFT_ON) != 0) { + meta &= ~KeyEvent.META_SHIFT_ON; + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SHIFT_LEFT, 0, meta, device, 0); + index++; + } + if ((mods & KeyEvent.META_SYM_ON) != 0) { + meta &= ~KeyEvent.META_SYM_ON; + rv[index] = new KeyEvent(now, now, KeyEvent.ACTION_UP, + KeyEvent.KEYCODE_SYM, 0, meta, device, 0); + index++; + } + } + + return rv; + } + + /** + * Does this character key produce a glyph? + */ + public boolean isPrintingKey(int keyCode) + { + int type = Character.getType(get(keyCode, 0)); + + switch (type) + { + case Character.SPACE_SEPARATOR: + case Character.LINE_SEPARATOR: + case Character.PARAGRAPH_SEPARATOR: + case Character.CONTROL: + case Character.FORMAT: + return false; + default: + return true; + } + } + + protected void finalize() throws Throwable + { + dtor_native(mPointer); + } + + /** + * Returns {@link #NUMERIC}, {@link #PREDICTIVE} or {@link #ALPHA}. + */ + public int getKeyboardType() + { + return getKeyboardType_native(mPointer); + } + + private int mPointer; + private int mKeyboardDevice; + + private static native int ctor_native(int id); + private static native void dtor_native(int ptr); + private static native char get_native(int ptr, int keycode, + int meta); + private static native char getNumber_native(int ptr, int keycode); + private static native char getMatch_native(int ptr, int keycode, + char[] chars, int modifiers); + private static native char getDisplayLabel_native(int ptr, int keycode); + private static native boolean getKeyData_native(int ptr, int keycode, + KeyData results); + private static native int getKeyboardType_native(int ptr); + private static native long[] getEvents_native(int ptr, char[] str); + + /** + * Maps Unicode combining diacritical to display-form dead key + * (display character shifted left 16 bits). + */ + private static SparseIntArray COMBINING = new SparseIntArray(); + + /** + * Maps combinations of (display-form) dead key and second character + * to combined output character. + */ + private static SparseIntArray DEAD = new SparseIntArray(); + + /* + * TODO: Change the table format to support full 21-bit-wide + * accent characters and combined characters if ever necessary. + */ + private static final int ACUTE = '\u00B4' << 16; + private static final int GRAVE = '`' << 16; + private static final int CIRCUMFLEX = '^' << 16; + private static final int TILDE = '~' << 16; + private static final int UMLAUT = '\u00A8' << 16; + + /* + * This bit will be set in the return value of {@link #get(int, int)} if the + * key is a "dead key." + */ + public static final int COMBINING_ACCENT = 0x80000000; + /** + * Mask the return value from {@link #get(int, int)} with this value to get + * a printable representation of the accent character of a "dead key." + */ + public static final int COMBINING_ACCENT_MASK = 0x7FFFFFFF; + + static { + COMBINING.put('\u0300', (GRAVE >> 16) | COMBINING_ACCENT); + COMBINING.put('\u0301', (ACUTE >> 16) | COMBINING_ACCENT); + COMBINING.put('\u0302', (CIRCUMFLEX >> 16) | COMBINING_ACCENT); + COMBINING.put('\u0303', (TILDE >> 16) | COMBINING_ACCENT); + COMBINING.put('\u0308', (UMLAUT >> 16) | COMBINING_ACCENT); + + DEAD.put(ACUTE | 'A', '\u00C1'); + DEAD.put(ACUTE | 'C', '\u0106'); + DEAD.put(ACUTE | 'E', '\u00C9'); + DEAD.put(ACUTE | 'G', '\u01F4'); + DEAD.put(ACUTE | 'I', '\u00CD'); + DEAD.put(ACUTE | 'K', '\u1E30'); + DEAD.put(ACUTE | 'L', '\u0139'); + DEAD.put(ACUTE | 'M', '\u1E3E'); + DEAD.put(ACUTE | 'N', '\u0143'); + DEAD.put(ACUTE | 'O', '\u00D3'); + DEAD.put(ACUTE | 'P', '\u1E54'); + DEAD.put(ACUTE | 'R', '\u0154'); + DEAD.put(ACUTE | 'S', '\u015A'); + DEAD.put(ACUTE | 'U', '\u00DA'); + DEAD.put(ACUTE | 'W', '\u1E82'); + DEAD.put(ACUTE | 'Y', '\u00DD'); + DEAD.put(ACUTE | 'Z', '\u0179'); + DEAD.put(ACUTE | 'a', '\u00E1'); + DEAD.put(ACUTE | 'c', '\u0107'); + DEAD.put(ACUTE | 'e', '\u00E9'); + DEAD.put(ACUTE | 'g', '\u01F5'); + DEAD.put(ACUTE | 'i', '\u00ED'); + DEAD.put(ACUTE | 'k', '\u1E31'); + DEAD.put(ACUTE | 'l', '\u013A'); + DEAD.put(ACUTE | 'm', '\u1E3F'); + DEAD.put(ACUTE | 'n', '\u0144'); + DEAD.put(ACUTE | 'o', '\u00F3'); + DEAD.put(ACUTE | 'p', '\u1E55'); + DEAD.put(ACUTE | 'r', '\u0155'); + DEAD.put(ACUTE | 's', '\u015B'); + DEAD.put(ACUTE | 'u', '\u00FA'); + DEAD.put(ACUTE | 'w', '\u1E83'); + DEAD.put(ACUTE | 'y', '\u00FD'); + DEAD.put(ACUTE | 'z', '\u017A'); + DEAD.put(CIRCUMFLEX | 'A', '\u00C2'); + DEAD.put(CIRCUMFLEX | 'C', '\u0108'); + DEAD.put(CIRCUMFLEX | 'E', '\u00CA'); + DEAD.put(CIRCUMFLEX | 'G', '\u011C'); + DEAD.put(CIRCUMFLEX | 'H', '\u0124'); + DEAD.put(CIRCUMFLEX | 'I', '\u00CE'); + DEAD.put(CIRCUMFLEX | 'J', '\u0134'); + DEAD.put(CIRCUMFLEX | 'O', '\u00D4'); + DEAD.put(CIRCUMFLEX | 'S', '\u015C'); + DEAD.put(CIRCUMFLEX | 'U', '\u00DB'); + DEAD.put(CIRCUMFLEX | 'W', '\u0174'); + DEAD.put(CIRCUMFLEX | 'Y', '\u0176'); + DEAD.put(CIRCUMFLEX | 'Z', '\u1E90'); + DEAD.put(CIRCUMFLEX | 'a', '\u00E2'); + DEAD.put(CIRCUMFLEX | 'c', '\u0109'); + DEAD.put(CIRCUMFLEX | 'e', '\u00EA'); + DEAD.put(CIRCUMFLEX | 'g', '\u011D'); + DEAD.put(CIRCUMFLEX | 'h', '\u0125'); + DEAD.put(CIRCUMFLEX | 'i', '\u00EE'); + DEAD.put(CIRCUMFLEX | 'j', '\u0135'); + DEAD.put(CIRCUMFLEX | 'o', '\u00F4'); + DEAD.put(CIRCUMFLEX | 's', '\u015D'); + DEAD.put(CIRCUMFLEX | 'u', '\u00FB'); + DEAD.put(CIRCUMFLEX | 'w', '\u0175'); + DEAD.put(CIRCUMFLEX | 'y', '\u0177'); + DEAD.put(CIRCUMFLEX | 'z', '\u1E91'); + DEAD.put(GRAVE | 'A', '\u00C0'); + DEAD.put(GRAVE | 'E', '\u00C8'); + DEAD.put(GRAVE | 'I', '\u00CC'); + DEAD.put(GRAVE | 'N', '\u01F8'); + DEAD.put(GRAVE | 'O', '\u00D2'); + DEAD.put(GRAVE | 'U', '\u00D9'); + DEAD.put(GRAVE | 'W', '\u1E80'); + DEAD.put(GRAVE | 'Y', '\u1EF2'); + DEAD.put(GRAVE | 'a', '\u00E0'); + DEAD.put(GRAVE | 'e', '\u00E8'); + DEAD.put(GRAVE | 'i', '\u00EC'); + DEAD.put(GRAVE | 'n', '\u01F9'); + DEAD.put(GRAVE | 'o', '\u00F2'); + DEAD.put(GRAVE | 'u', '\u00F9'); + DEAD.put(GRAVE | 'w', '\u1E81'); + DEAD.put(GRAVE | 'y', '\u1EF3'); + DEAD.put(TILDE | 'A', '\u00C3'); + DEAD.put(TILDE | 'E', '\u1EBC'); + DEAD.put(TILDE | 'I', '\u0128'); + DEAD.put(TILDE | 'N', '\u00D1'); + DEAD.put(TILDE | 'O', '\u00D5'); + DEAD.put(TILDE | 'U', '\u0168'); + DEAD.put(TILDE | 'V', '\u1E7C'); + DEAD.put(TILDE | 'Y', '\u1EF8'); + DEAD.put(TILDE | 'a', '\u00E3'); + DEAD.put(TILDE | 'e', '\u1EBD'); + DEAD.put(TILDE | 'i', '\u0129'); + DEAD.put(TILDE | 'n', '\u00F1'); + DEAD.put(TILDE | 'o', '\u00F5'); + DEAD.put(TILDE | 'u', '\u0169'); + DEAD.put(TILDE | 'v', '\u1E7D'); + DEAD.put(TILDE | 'y', '\u1EF9'); + DEAD.put(UMLAUT | 'A', '\u00C4'); + DEAD.put(UMLAUT | 'E', '\u00CB'); + DEAD.put(UMLAUT | 'H', '\u1E26'); + DEAD.put(UMLAUT | 'I', '\u00CF'); + DEAD.put(UMLAUT | 'O', '\u00D6'); + DEAD.put(UMLAUT | 'U', '\u00DC'); + DEAD.put(UMLAUT | 'W', '\u1E84'); + DEAD.put(UMLAUT | 'X', '\u1E8C'); + DEAD.put(UMLAUT | 'Y', '\u0178'); + DEAD.put(UMLAUT | 'a', '\u00E4'); + DEAD.put(UMLAUT | 'e', '\u00EB'); + DEAD.put(UMLAUT | 'h', '\u1E27'); + DEAD.put(UMLAUT | 'i', '\u00EF'); + DEAD.put(UMLAUT | 'o', '\u00F6'); + DEAD.put(UMLAUT | 't', '\u1E97'); + DEAD.put(UMLAUT | 'u', '\u00FC'); + DEAD.put(UMLAUT | 'w', '\u1E85'); + DEAD.put(UMLAUT | 'x', '\u1E8D'); + DEAD.put(UMLAUT | 'y', '\u00FF'); + } +} diff --git a/core/java/android/view/KeyEvent.aidl b/core/java/android/view/KeyEvent.aidl new file mode 100644 index 0000000..dc15ecf --- /dev/null +++ b/core/java/android/view/KeyEvent.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android.view.KeyEvent.aidl +** +** Copyright 2007, 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.view; + +parcelable KeyEvent; diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java new file mode 100644 index 0000000..24b0316 --- /dev/null +++ b/core/java/android/view/KeyEvent.java @@ -0,0 +1,786 @@ +/* + * Copyright (C) 2006 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.view; + +import android.os.Parcel; +import android.os.Parcelable; +import android.view.KeyCharacterMap; +import android.view.KeyCharacterMap.KeyData; + +/** + * Contains constants for key events. + */ +public class KeyEvent implements Parcelable { + // key codes + public static final int KEYCODE_UNKNOWN = 0; + public static final int KEYCODE_SOFT_LEFT = 1; + public static final int KEYCODE_SOFT_RIGHT = 2; + public static final int KEYCODE_HOME = 3; + public static final int KEYCODE_BACK = 4; + public static final int KEYCODE_CALL = 5; + public static final int KEYCODE_ENDCALL = 6; + public static final int KEYCODE_0 = 7; + public static final int KEYCODE_1 = 8; + public static final int KEYCODE_2 = 9; + public static final int KEYCODE_3 = 10; + public static final int KEYCODE_4 = 11; + public static final int KEYCODE_5 = 12; + public static final int KEYCODE_6 = 13; + public static final int KEYCODE_7 = 14; + public static final int KEYCODE_8 = 15; + public static final int KEYCODE_9 = 16; + public static final int KEYCODE_STAR = 17; + public static final int KEYCODE_POUND = 18; + public static final int KEYCODE_DPAD_UP = 19; + public static final int KEYCODE_DPAD_DOWN = 20; + public static final int KEYCODE_DPAD_LEFT = 21; + public static final int KEYCODE_DPAD_RIGHT = 22; + public static final int KEYCODE_DPAD_CENTER = 23; + public static final int KEYCODE_VOLUME_UP = 24; + public static final int KEYCODE_VOLUME_DOWN = 25; + public static final int KEYCODE_POWER = 26; + public static final int KEYCODE_CAMERA = 27; + public static final int KEYCODE_CLEAR = 28; + public static final int KEYCODE_A = 29; + public static final int KEYCODE_B = 30; + public static final int KEYCODE_C = 31; + public static final int KEYCODE_D = 32; + public static final int KEYCODE_E = 33; + public static final int KEYCODE_F = 34; + public static final int KEYCODE_G = 35; + public static final int KEYCODE_H = 36; + public static final int KEYCODE_I = 37; + public static final int KEYCODE_J = 38; + public static final int KEYCODE_K = 39; + public static final int KEYCODE_L = 40; + public static final int KEYCODE_M = 41; + public static final int KEYCODE_N = 42; + public static final int KEYCODE_O = 43; + public static final int KEYCODE_P = 44; + public static final int KEYCODE_Q = 45; + public static final int KEYCODE_R = 46; + public static final int KEYCODE_S = 47; + public static final int KEYCODE_T = 48; + public static final int KEYCODE_U = 49; + public static final int KEYCODE_V = 50; + public static final int KEYCODE_W = 51; + public static final int KEYCODE_X = 52; + public static final int KEYCODE_Y = 53; + public static final int KEYCODE_Z = 54; + public static final int KEYCODE_COMMA = 55; + public static final int KEYCODE_PERIOD = 56; + public static final int KEYCODE_ALT_LEFT = 57; + public static final int KEYCODE_ALT_RIGHT = 58; + public static final int KEYCODE_SHIFT_LEFT = 59; + public static final int KEYCODE_SHIFT_RIGHT = 60; + public static final int KEYCODE_TAB = 61; + public static final int KEYCODE_SPACE = 62; + public static final int KEYCODE_SYM = 63; + public static final int KEYCODE_EXPLORER = 64; + public static final int KEYCODE_ENVELOPE = 65; + public static final int KEYCODE_ENTER = 66; + public static final int KEYCODE_DEL = 67; + public static final int KEYCODE_GRAVE = 68; + public static final int KEYCODE_MINUS = 69; + public static final int KEYCODE_EQUALS = 70; + public static final int KEYCODE_LEFT_BRACKET = 71; + public static final int KEYCODE_RIGHT_BRACKET = 72; + public static final int KEYCODE_BACKSLASH = 73; + public static final int KEYCODE_SEMICOLON = 74; + public static final int KEYCODE_APOSTROPHE = 75; + public static final int KEYCODE_SLASH = 76; + public static final int KEYCODE_AT = 77; + public static final int KEYCODE_NUM = 78; + public static final int KEYCODE_HEADSETHOOK = 79; + public static final int KEYCODE_FOCUS = 80; // *Camera* focus + public static final int KEYCODE_PLUS = 81; + public static final int KEYCODE_MENU = 82; + public static final int KEYCODE_NOTIFICATION = 83; + public static final int KEYCODE_SEARCH = 84; + + // NOTE: If you add a new keycode here you must also add it to: + // isSystem() + // include/ui/KeycodeLabels.h + // tools/puppet_master/PuppetMaster/nav_keys.py + // apps/common/res/values/attrs.xml + // commands/monkey/Monkey.java + // emulator? + + public static final int MAX_KEYCODE = 84; + + /** + * {@link #getAction} value: the key has been pressed down. + */ + public static final int ACTION_DOWN = 0; + /** + * {@link #getAction} value: the key has been released. + */ + public static final int ACTION_UP = 1; + /** + * {@link #getAction} value: multiple duplicate key events have + * occurred in a row. The {#link {@link #getRepeatCount()} method returns + * the number of duplicates. + */ + public static final int ACTION_MULTIPLE = 2; + + /** + * <p>This mask is used to check whether one of the ALT meta keys is pressed.</p> + * + * @see #isAltPressed() + * @see #getMetaState() + * @see #KEYCODE_ALT_LEFT + * @see #KEYCODE_ALT_RIGHT + */ + public static final int META_ALT_ON = 0x02; + + /** + * <p>This mask is used to check whether the left ALT meta key is pressed.</p> + * + * @see #isAltPressed() + * @see #getMetaState() + * @see #KEYCODE_ALT_LEFT + */ + public static final int META_ALT_LEFT_ON = 0x10; + + /** + * <p>This mask is used to check whether the right the ALT meta key is pressed.</p> + * + * @see #isAltPressed() + * @see #getMetaState() + * @see #KEYCODE_ALT_RIGHT + */ + public static final int META_ALT_RIGHT_ON = 0x20; + + /** + * <p>This mask is used to check whether one of the SHIFT meta keys is pressed.</p> + * + * @see #isShiftPressed() + * @see #getMetaState() + * @see #KEYCODE_SHIFT_LEFT + * @see #KEYCODE_SHIFT_RIGHT + */ + public static final int META_SHIFT_ON = 0x1; + + /** + * <p>This mask is used to check whether the left SHIFT meta key is pressed.</p> + * + * @see #isShiftPressed() + * @see #getMetaState() + * @see #KEYCODE_SHIFT_LEFT + */ + public static final int META_SHIFT_LEFT_ON = 0x40; + + /** + * <p>This mask is used to check whether the right SHIFT meta key is pressed.</p> + * + * @see #isShiftPressed() + * @see #getMetaState() + * @see #KEYCODE_SHIFT_RIGHT + */ + public static final int META_SHIFT_RIGHT_ON = 0x80; + + /** + * <p>This mask is used to check whether the SYM meta key is pressed.</p> + * + * @see #isSymPressed() + * @see #getMetaState() + */ + public static final int META_SYM_ON = 0x4; + + /** + * This mask is set if the device woke because of this key event. + */ + public static final int FLAG_WOKE_HERE = 0x1; + + /** + * Get the character that is produced by putting accent on the character + * c. + * For example, getDeadChar('`', 'e') returns è. + */ + public static int getDeadChar(int accent, int c) { + return KeyCharacterMap.getDeadChar(accent, c); + } + + private int mMetaState; + private int mAction; + private int mKeyCode; + private int mScancode; + private int mRepeatCount; + private int mDeviceId; + private int mFlags; + private long mDownTime; + private long mEventTime; + + public interface Callback { + /** + * Called when a key down event has occurred. + * + * @param keyCode The value in event.getKeyCode(). + * @param event Description of the key event. + * + * @return If you handled the event, return true. If you want to allow + * the event to be handled by the next receiver, return false. + */ + boolean onKeyDown(int keyCode, KeyEvent event); + + /** + * Called when a key up event has occurred. + * + * @param keyCode The value in event.getKeyCode(). + * @param event Description of the key event. + * + * @return If you handled the event, return true. If you want to allow + * the event to be handled by the next receiver, return false. + */ + boolean onKeyUp(int keyCode, KeyEvent event); + + /** + * Called when multiple down/up pairs of the same key have occurred + * in a row. + * + * @param keyCode The value in event.getKeyCode(). + * @param count Number of pairs as returned by event.getRepeatCount(). + * @param event Description of the key event. + * + * @return If you handled the event, return true. If you want to allow + * the event to be handled by the next receiver, return false. + */ + boolean onKeyMultiple(int keyCode, int count, KeyEvent event); + } + + /** + * Create a new key event. + * + * @param action Action code: either {@link #ACTION_DOWN}, + * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. + * @param code The key code. + */ + public KeyEvent(int action, int code) { + mAction = action; + mKeyCode = code; + mRepeatCount = 0; + } + + /** + * Create a new key event. + * + * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this key code originally went down. + * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this event happened. + * @param action Action code: either {@link #ACTION_DOWN}, + * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. + * @param code The key code. + * @param repeat A repeat count for down events (> 0 if this is after the + * initial down) or event count for multiple events. + */ + public KeyEvent(long downTime, long eventTime, int action, + int code, int repeat) { + mDownTime = downTime; + mEventTime = eventTime; + mAction = action; + mKeyCode = code; + mRepeatCount = repeat; + } + + /** + * Create a new key event. + * + * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this key code originally went down. + * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this event happened. + * @param action Action code: either {@link #ACTION_DOWN}, + * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. + * @param code The key code. + * @param repeat A repeat count for down events (> 0 if this is after the + * initial down) or event count for multiple events. + * @param metaState Flags indicating which meta keys are currently pressed. + */ + public KeyEvent(long downTime, long eventTime, int action, + int code, int repeat, int metaState) { + mDownTime = downTime; + mEventTime = eventTime; + mAction = action; + mKeyCode = code; + mRepeatCount = repeat; + mMetaState = metaState; + } + + /** + * Create a new key event. + * + * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this key code originally went down. + * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this event happened. + * @param action Action code: either {@link #ACTION_DOWN}, + * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. + * @param code The key code. + * @param repeat A repeat count for down events (> 0 if this is after the + * initial down) or event count for multiple events. + * @param metaState Flags indicating which meta keys are currently pressed. + * @param device The device ID that generated the key event. + * @param scancode Raw device scan code of the event. + */ + public KeyEvent(long downTime, long eventTime, int action, + int code, int repeat, int metaState, + int device, int scancode) { + mDownTime = downTime; + mEventTime = eventTime; + mAction = action; + mKeyCode = code; + mRepeatCount = repeat; + mMetaState = metaState; + mDeviceId = device; + mScancode = scancode; + } + + /** + * Create a new key event. + * + * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this key code originally went down. + * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this event happened. + * @param action Action code: either {@link #ACTION_DOWN}, + * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. + * @param code The key code. + * @param repeat A repeat count for down events (> 0 if this is after the + * initial down) or event count for multiple events. + * @param metaState Flags indicating which meta keys are currently pressed. + * @param device The device ID that generated the key event. + * @param scancode Raw device scan code of the event. + * @param flags The flags for this key event + */ + public KeyEvent(long downTime, long eventTime, int action, + int code, int repeat, int metaState, + int device, int scancode, int flags) { + mDownTime = downTime; + mEventTime = eventTime; + mAction = action; + mKeyCode = code; + mRepeatCount = repeat; + mMetaState = metaState; + mDeviceId = device; + mScancode = scancode; + mFlags = flags; + } + + /** + * Copy an existing key event, modifying its time and repeat count. + * + * @param origEvent The existing event to be copied. + * @param eventTime The new event time + * (in {@link android.os.SystemClock#uptimeMillis}) of the event. + * @param newRepeat The new repeat count of the event. + */ + public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) { + mDownTime = origEvent.mDownTime; + mEventTime = eventTime; + mAction = origEvent.mAction; + mKeyCode = origEvent.mKeyCode; + mRepeatCount = newRepeat; + mMetaState = origEvent.mMetaState; + mDeviceId = origEvent.mDeviceId; + mScancode = origEvent.mScancode; + mFlags = origEvent.mFlags; + } + + /** + * Don't use in new code, instead explicitly check + * {@link #getAction()}. + * + * @return If the action is ACTION_DOWN, returns true; else false. + * + * @deprecated + * @hide + */ + @Deprecated public final boolean isDown() { + return mAction == ACTION_DOWN; + } + + /** + * Is this a system key? System keys can not be used for menu shortcuts. + * + * TODO: this information should come from a table somewhere. + * TODO: should the dpad keys be here? arguably, because they also shouldn't be menu shortcuts + */ + public final boolean isSystem() { + switch (mKeyCode) { + case KEYCODE_MENU: + case KEYCODE_SOFT_RIGHT: + case KEYCODE_HOME: + case KEYCODE_BACK: + case KEYCODE_CALL: + case KEYCODE_ENDCALL: + case KEYCODE_VOLUME_UP: + case KEYCODE_VOLUME_DOWN: + case KEYCODE_POWER: + case KEYCODE_HEADSETHOOK: + case KEYCODE_CAMERA: + case KEYCODE_FOCUS: + case KEYCODE_SEARCH: + return true; + default: + return false; + } + } + + + /** + * <p>Returns the state of the meta keys.</p> + * + * @return an integer in which each bit set to 1 represents a pressed + * meta key + * + * @see #isAltPressed() + * @see #isShiftPressed() + * @see #isSymPressed() + * @see #META_ALT_ON + * @see #META_SHIFT_ON + * @see #META_SYM_ON + */ + public final int getMetaState() { + return mMetaState; + } + + /** + * Returns the flags for this key event. + * + * @see #FLAG_WOKE_HERE + */ + public final int getFlags() { + return mFlags; + } + + /** + * Returns true if this key code is a modifier key. + * + * @return whether the provided keyCode is one of + * {@link #KEYCODE_SHIFT_LEFT} {@link #KEYCODE_SHIFT_RIGHT}, + * {@link #KEYCODE_ALT_LEFT}, {@link #KEYCODE_ALT_RIGHT} + * or {@link #KEYCODE_SYM}. + */ + public static boolean isModifierKey(int keyCode) { + return keyCode == KEYCODE_SHIFT_LEFT || keyCode == KEYCODE_SHIFT_RIGHT + || keyCode == KEYCODE_ALT_LEFT || keyCode == KEYCODE_ALT_RIGHT + || keyCode == KEYCODE_SYM; + } + + /** + * <p>Returns the pressed state of the ALT meta key.</p> + * + * @return true if the ALT key is pressed, false otherwise + * + * @see #KEYCODE_ALT_LEFT + * @see #KEYCODE_ALT_RIGHT + * @see #META_ALT_ON + */ + public final boolean isAltPressed() { + return (mMetaState & META_ALT_ON) != 0; + } + + /** + * <p>Returns the pressed state of the SHIFT meta key.</p> + * + * @return true if the SHIFT key is pressed, false otherwise + * + * @see #KEYCODE_SHIFT_LEFT + * @see #KEYCODE_SHIFT_RIGHT + * @see #META_SHIFT_ON + */ + public final boolean isShiftPressed() { + return (mMetaState & META_SHIFT_ON) != 0; + } + + /** + * <p>Returns the pressed state of the SYM meta key.</p> + * + * @return true if the SYM key is pressed, false otherwise + * + * @see #KEYCODE_SYM + * @see #META_SYM_ON + */ + public final boolean isSymPressed() { + return (mMetaState & META_SYM_ON) != 0; + } + + /** + * Retrieve the action of this key event. May be either + * {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. + * + * @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE. + */ + public final int getAction() { + return mAction; + } + + /** + * Retrieve the key code of the key event. This is the physical key that + * was pressed -- not the Unicode character. + * + * @return The key code of the event. + */ + public final int getKeyCode() { + return mKeyCode; + } + + /** + * Retrieve the hardware key id of this key event. These values are not + * reliable and vary from device to device. + * + * {@more} + * Mostly this is here for debugging purposes. + */ + public final int getScanCode() { + return mScancode; + } + + /** + * Retrieve the repeat count of the event. For both key up and key down + * events, this is the number of times the key has repeated with the first + * down starting at 0 and counting up from there. For multiple key + * events, this is the number of down/up pairs that have occurred. + * + * @return The number of times the key has repeated. + */ + public final int getRepeatCount() { + return mRepeatCount; + } + + /** + * Retrieve the time of the most recent key down event, + * in the {@link android.os.SystemClock#uptimeMillis} time base. If this + * is a down event, this will be the same as {@link #getEventTime()}. + * Note that when chording keys, this value is the down time of the + * most recently pressed key, which may <em>not</em> be the same physical + * key of this event. + * + * @return Returns the most recent key down time, in the + * {@link android.os.SystemClock#uptimeMillis} time base + */ + public final long getDownTime() { + return mDownTime; + } + + /** + * Retrieve the time this event occurred, + * in the {@link android.os.SystemClock#uptimeMillis} time base. + * + * @return Returns the time this event occurred, + * in the {@link android.os.SystemClock#uptimeMillis} time base. + */ + public final long getEventTime() { + return mEventTime; + } + + /** + * Return the id for the keyboard that this event came from. A device + * id of 0 indicates the event didn't come from a physical device and + * maps to the default keymap. The other numbers are arbitrary and + * you shouldn't depend on the values. + * + * @see KeyCharacterMap#load + */ + public final int getDeviceId() { + return mDeviceId; + } + + /** + * Renamed to {@link #getDeviceId}. + * + * @hide + * @deprecated + */ + public final int getKeyboardDevice() { + return mDeviceId; + } + + /** + * Get the primary character for this key. In other words, the label + * that is physically printed on it. + */ + public char getDisplayLabel() { + return KeyCharacterMap.load(mDeviceId).getDisplayLabel(mKeyCode); + } + + /** + * <p> + * Returns the Unicode character that the key would produce. + * </p><p> + * Returns 0 if the key is not one that is used to type Unicode + * characters. + * </p><p> + * If the return value has bit + * {@link KeyCharacterMap#COMBINING_ACCENT} + * set, the key is a "dead key" that should be combined with another to + * actually produce a character -- see {@link #getDeadChar} -- + * after masking with + * {@link KeyCharacterMap#COMBINING_ACCENT_MASK}. + * </p> + */ + public int getUnicodeChar() { + return getUnicodeChar(mMetaState); + } + + /** + * <p> + * Returns the Unicode character that the key would produce. + * </p><p> + * Returns 0 if the key is not one that is used to type Unicode + * characters. + * </p><p> + * If the return value has bit + * {@link KeyCharacterMap#COMBINING_ACCENT} + * set, the key is a "dead key" that should be combined with another to + * actually produce a character -- see {@link #getDeadChar} -- after masking + * with {@link KeyCharacterMap#COMBINING_ACCENT_MASK}. + * </p> + */ + public int getUnicodeChar(int meta) { + return KeyCharacterMap.load(mDeviceId).get(mKeyCode, meta); + } + + /** + * Get the characters conversion data for the key event.. + * + * @param results a {@link KeyData} that will be filled with the results. + * + * @return whether the key was mapped or not. If the key was not mapped, + * results is not modified. + */ + public boolean getKeyData(KeyData results) { + return KeyCharacterMap.load(mDeviceId).getKeyData(mKeyCode, results); + } + + /** + * The same as {@link #getMatch(char[],int) getMatch(chars, 0)}. + */ + public char getMatch(char[] chars) { + return getMatch(chars, 0); + } + + /** + * If one of the chars in the array can be generated by the keyCode of this + * key event, return the char; otherwise return '\0'. + * @param chars the characters to try to find + * @param modifiers the modifier bits to prefer. If any of these bits + * are set, if there are multiple choices, that could + * work, the one for this modifier will be set. + */ + public char getMatch(char[] chars, int modifiers) { + return KeyCharacterMap.load(mDeviceId).getMatch(mKeyCode, chars, modifiers); + } + + /** + * Gets the number or symbol associated with the key. The character value + * is returned, not the numeric value. If the key is not a number, but is + * a symbol, the symbol is retuned. + */ + public char getNumber() { + return KeyCharacterMap.load(mDeviceId).getNumber(mKeyCode); + } + + /** + * Does the key code of this key produce a glyph? + */ + public boolean isPrintingKey() { + return KeyCharacterMap.load(mDeviceId).isPrintingKey(mKeyCode); + } + + /** + * Deliver this key event to a {@link Callback} interface. If this is + * an ACTION_MULTIPLE event and it is not handled, then an attempt will + * be made to deliver a single normal event. + * + * @param receiver The Callback that will be given the event. + * + * @return The return value from the Callback method that was called. + */ + public final boolean dispatch(Callback receiver) { + switch (mAction) { + case ACTION_DOWN: + return receiver.onKeyDown(mKeyCode, this); + case ACTION_UP: + return receiver.onKeyUp(mKeyCode, this); + case ACTION_MULTIPLE: + final int count = mRepeatCount; + final int code = mKeyCode; + if (receiver.onKeyMultiple(code, count, this)) { + return true; + } + mAction = ACTION_DOWN; + mRepeatCount = 0; + boolean handled = receiver.onKeyDown(code, this); + if (handled) { + mAction = ACTION_UP; + receiver.onKeyUp(code, this); + } + mAction = ACTION_MULTIPLE; + mRepeatCount = count; + return handled; + } + return false; + } + + public String toString() { + return "KeyEvent{action=" + mAction + " code=" + mKeyCode + + " repeat=" + mRepeatCount + + " meta=" + mMetaState + " scancode=" + mScancode + + " mFlags=" + mFlags + "}"; + } + + public static final Parcelable.Creator<KeyEvent> CREATOR + = new Parcelable.Creator<KeyEvent>() { + public KeyEvent createFromParcel(Parcel in) { + return new KeyEvent(in); + } + + public KeyEvent[] newArray(int size) { + return new KeyEvent[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mAction); + out.writeInt(mKeyCode); + out.writeInt(mRepeatCount); + out.writeInt(mMetaState); + out.writeInt(mDeviceId); + out.writeInt(mScancode); + out.writeInt(mFlags); + out.writeLong(mDownTime); + out.writeLong(mEventTime); + } + + private KeyEvent(Parcel in) { + mAction = in.readInt(); + mKeyCode = in.readInt(); + mRepeatCount = in.readInt(); + mMetaState = in.readInt(); + mDeviceId = in.readInt(); + mScancode = in.readInt(); + mFlags = in.readInt(); + mDownTime = in.readLong(); + mEventTime = in.readLong(); + } +} diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java new file mode 100644 index 0000000..94acd3f --- /dev/null +++ b/core/java/android/view/LayoutInflater.java @@ -0,0 +1,744 @@ +/* + * Copyright (C) 2007 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.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.HashMap; + +/** + * This class is used to instantiate layout XML file into its corresponding View + * objects. It is never be used directly -- use + * {@link android.app.Activity#getLayoutInflater()} or + * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance + * that is already hooked up to the current context and correctly configured + * for the device you are running on. For example: + * + * <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService + * Context.LAYOUT_INFLATER_SERVICE);</pre> + * + * <p> + * To create a new LayoutInflater with an additional {@link Factory} for your + * own views, you can use {@link #cloneInContext} to clone an existing + * ViewFactory, and then call {@link #setFactory} on it to include your + * Factory. + * + * <p> + * For performance reasons, view inflation relies heavily on pre-processing of + * XML files that is done at build time. Therefore, it is not currently possible + * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime; + * it only works with an XmlPullParser returned from a compiled resource + * (R.<em>something</em> file.) + * + * @see Context#getSystemService + */ +public abstract class LayoutInflater { + private final boolean DEBUG = false; + + /** + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected final Context mContext; + + // these are optional, set by the caller + private boolean mFactorySet; + private Factory mFactory; + private Filter mFilter; + + private final Object[] mConstructorArgs = new Object[2]; + + private static final Class[] mConstructorSignature = new Class[] { + Context.class, AttributeSet.class}; + + private static final HashMap<String, Constructor> sConstructorMap = + new HashMap<String, Constructor>(); + + private HashMap<String, Boolean> mFilterMap; + + private static final String TAG_MERGE = "merge"; + private static final String TAG_INCLUDE = "include"; + private static final String TAG_REQUEST_FOCUS = "requestFocus"; + + /** + * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed + * to be inflated. + * + */ + public interface Filter { + /** + * Hook to allow clients of the LayoutInflater to restrict the set of Views + * that are allowed to be inflated. + * + * @param clazz The class object for the View that is about to be inflated + * + * @return True if this class is allowed to be inflated, or false otherwise + */ + boolean onLoadClass(Class clazz); + } + + public interface Factory { + /** + * Hook you can supply that is called when inflating from a LayoutInflater. + * You can use this to customize the tag names available in your XML + * layout files. + * + * <p> + * Note that it is good practice to prefix these custom names with your + * package (i.e., com.coolcompany.apps) to avoid conflicts with system + * names. + * + * @param name Tag name to be inflated. + * @param context The context the view is being created in. + * @param attrs Inflation attributes as specified in XML file. + * + * @return View Newly created view. Return null for the default + * behavior. + */ + public View onCreateView(String name, Context context, AttributeSet attrs); + } + + private static class FactoryMerger implements Factory { + private final Factory mF1, mF2; + + FactoryMerger(Factory f1, Factory f2) { + mF1 = f1; + mF2 = f2; + } + + public View onCreateView(String name, Context context, AttributeSet attrs) { + View v = mF1.onCreateView(name, context, attrs); + if (v != null) return v; + return mF2.onCreateView(name, context, attrs); + } + } + + /** + * Create a new LayoutInflater instance associated with a particular Context. + * Applications will almost always want to use + * {@link Context#getSystemService Context.getSystemService()} to retrieve + * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}. + * + * @param context The Context in which this LayoutInflater will create its + * Views; most importantly, this supplies the theme from which the default + * values for their attributes are retrieved. + */ + protected LayoutInflater(Context context) { + mContext = context; + } + + /** + * Create a new LayoutInflater instance that is a copy of an existing + * LayoutInflater, optionally with its Context changed. For use in + * implementing {@link #cloneInContext}. + * + * @param original The original LayoutInflater to copy. + * @param newContext The new Context to use. + */ + protected LayoutInflater(LayoutInflater original, Context newContext) { + mContext = newContext; + mFactory = original.mFactory; + mFilter = original.mFilter; + } + + /** + * Obtains the LayoutInflater from the given context. + */ + public static LayoutInflater from(Context context) { + LayoutInflater LayoutInflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + if (LayoutInflater == null) { + throw new AssertionError("LayoutInflater not found."); + } + return LayoutInflater; + } + + /** + * Create a copy of the existing LayoutInflater object, with the copy + * pointing to a different Context than the original. This is used by + * {@link ContextThemeWrapper} to create a new LayoutInflater to go along + * with the new Context theme. + * + * @param newContext The new Context to associate with the new LayoutInflater. + * May be the same as the original Context if desired. + * + * @return Returns a brand spanking new LayoutInflater object associated with + * the given Context. + */ + public abstract LayoutInflater cloneInContext(Context newContext); + + /** + * Return the context we are running in, for access to resources, class + * loader, etc. + */ + public Context getContext() { + return mContext; + } + + /** + * Return the current factory (or null). This is called on each element + * name. If the factory returns a View, add that to the hierarchy. If it + * returns null, proceed to call onCreateView(name). + */ + public final Factory getFactory() { + return mFactory; + } + + /** + * Attach a custom Factory interface for creating views while using + * this LayoutInflater. This must not be null, and can only be set once; + * after setting, you can not change the factory. This is + * called on each element name as the xml is parsed. If the factory returns + * a View, that is added to the hierarchy. If it returns null, the next + * factory default {@link #onCreateView} method is called. + * + * <p>If you have an existing + * LayoutInflater and want to add your own factory to it, use + * {@link #cloneInContext} to clone the existing instance and then you + * can use this function (once) on the returned new instance. This will + * merge your own factory with whatever factory the original instance is + * using. + */ + public void setFactory(Factory factory) { + if (mFactorySet) { + throw new IllegalStateException("A factory has already been set on this LayoutInflater"); + } + if (factory == null) { + throw new NullPointerException("Given factory can not be null"); + } + mFactorySet = true; + if (mFactory == null) { + mFactory = factory; + } else { + mFactory = new FactoryMerger(factory, mFactory); + } + } + + /** + * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views + * that are allowed to be inflated. + */ + public Filter getFilter() { + return mFilter; + } + + /** + * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated + * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will + * throw an {@link InflateException}. This filter will replace any previous filter set on this + * LayoutInflater. + * + * @param filter The Filter which restricts the set of Views that are allowed to be inflated. + * This filter will replace any previous filter set on this LayoutInflater. + */ + public void setFilter(Filter filter) { + mFilter = filter; + if (filter != null) { + mFilterMap = new HashMap<String, Boolean>(); + } + } + + /** + * Inflate a new view hierarchy from the specified xml resource. Throws + * {@link InflateException} if there is an error. + * + * @param resource ID for an XML layout resource to load (e.g., + * <code>R.layout.main_page</code>) + * @param root Optional view to be the parent of the generated hierarchy. + * @return The root View of the inflated hierarchy. If root was supplied, + * this is the root View; otherwise it is the root of the inflated + * XML file. + */ + public View inflate(int resource, ViewGroup root) { + return inflate(resource, root, root != null); + } + + /** + * Inflate a new view hierarchy from the specified xml node. Throws + * {@link InflateException} if there is an error. * + * <p> + * <em><strong>Important</strong></em> For performance + * reasons, view inflation relies heavily on pre-processing of XML files + * that is done at build time. Therefore, it is not currently possible to + * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. + * + * @param parser XML dom node containing the description of the view + * hierarchy. + * @param root Optional view to be the parent of the generated hierarchy. + * @return The root View of the inflated hierarchy. If root was supplied, + * this is the root View; otherwise it is the root of the inflated + * XML file. + */ + public View inflate(XmlPullParser parser, ViewGroup root) { + return inflate(parser, root, root != null); + } + + /** + * Inflate a new view hierarchy from the specified xml resource. Throws + * {@link InflateException} if there is an error. + * + * @param resource ID for an XML layout resource to load (e.g., + * <code>R.layout.main_page</code>) + * @param root Optional view to be the parent of the generated hierarchy (if + * <em>attachToRoot</em> is true), or else simply an object that + * provides a set of LayoutParams values for root of the returned + * hierarchy (if <em>attachToRoot</em> is false.) + * @param attachToRoot Whether the inflated hierarchy should be attached to + * the root parameter? If false, root is only used to create the + * correct subclass of LayoutParams for the root view in the XML. + * @return The root View of the inflated hierarchy. If root was supplied and + * attachToRoot is true, this is root; otherwise it is the root of + * the inflated XML file. + */ + public View inflate(int resource, ViewGroup root, boolean attachToRoot) { + if (DEBUG) System.out.println("INFLATING from resource: " + resource); + XmlResourceParser parser = getContext().getResources().getLayout(resource); + try { + return inflate(parser, root, attachToRoot); + } finally { + parser.close(); + } + } + + /** + * Inflate a new view hierarchy from the specified XML node. Throws + * {@link InflateException} if there is an error. + * <p> + * <em><strong>Important</strong></em> For performance + * reasons, view inflation relies heavily on pre-processing of XML files + * that is done at build time. Therefore, it is not currently possible to + * use LayoutInflater with an XmlPullParser over a plain XML file at runtime. + * + * @param parser XML dom node containing the description of the view + * hierarchy. + * @param root Optional view to be the parent of the generated hierarchy (if + * <em>attachToRoot</em> is true), or else simply an object that + * provides a set of LayoutParams values for root of the returned + * hierarchy (if <em>attachToRoot</em> is false.) + * @param attachToRoot Whether the inflated hierarchy should be attached to + * the root parameter? If false, root is only used to create the + * correct subclass of LayoutParams for the root view in the XML. + * @return The root View of the inflated hierarchy. If root was supplied and + * attachToRoot is true, this is root; otherwise it is the root of + * the inflated XML file. + */ + public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { + synchronized (mConstructorArgs) { + final AttributeSet attrs = Xml.asAttributeSet(parser); + mConstructorArgs[0] = mContext; + View result = root; + + try { + // Look for the root node. + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && + type != XmlPullParser.END_DOCUMENT) { + // Empty + } + + if (type != XmlPullParser.START_TAG) { + throw new InflateException(parser.getPositionDescription() + + ": No start tag found!"); + } + + final String name = parser.getName(); + + if (DEBUG) { + System.out.println("**************************"); + System.out.println("Creating root view: " + + name); + System.out.println("**************************"); + } + + if (TAG_MERGE.equals(name)) { + if (root == null || !attachToRoot) { + throw new InflateException("<merge /> can be used only with a valid " + + "ViewGroup root and attachToRoot=true"); + } + + rInflate(parser, root, attrs); + } else { + // Temp is the root view that was found in the xml + View temp = createViewFromTag(name, attrs); + + ViewGroup.LayoutParams params = null; + + if (root != null) { + if (DEBUG) { + System.out.println("Creating params from root: " + + root); + } + // Create layout params that match root, if supplied + params = root.generateLayoutParams(attrs); + if (!attachToRoot) { + // Set the layout params for temp if we are not + // attaching. (If we are, we use addView, below) + temp.setLayoutParams(params); + } + } + + if (DEBUG) { + System.out.println("-----> start inflating children"); + } + // Inflate all children under temp + rInflate(parser, temp, attrs); + if (DEBUG) { + System.out.println("-----> done inflating children"); + } + + // We are supposed to attach all the views we found (int temp) + // to root. Do that now. + if (root != null && attachToRoot) { + root.addView(temp, params); + } + + // Decide whether to return the root that was passed in or the + // top view found in xml. + if (root == null || !attachToRoot) { + result = temp; + } + } + + } catch (XmlPullParserException e) { + InflateException ex = new InflateException(e.getMessage()); + ex.initCause(e); + throw ex; + } catch (IOException e) { + InflateException ex = new InflateException( + parser.getPositionDescription() + + ": " + e.getMessage()); + ex.initCause(e); + throw ex; + } + + return result; + } + } + + /** + * Low-level function for instantiating a view by name. This attempts to + * instantiate a view class of the given <var>name</var> found in this + * LayoutInflater's ClassLoader. + * + * <p> + * There are two things that can happen in an error case: either the + * exception describing the error will be thrown, or a null will be + * returned. You must deal with both possibilities -- the former will happen + * the first time createView() is called for a class of a particular name, + * the latter every time there-after for that class name. + * + * @param name The full name of the class to be instantiated. + * @param attrs The XML attributes supplied for this instance. + * + * @return View The newly instantied view, or null. + */ + public final View createView(String name, String prefix, AttributeSet attrs) + throws ClassNotFoundException, InflateException { + Constructor constructor = sConstructorMap.get(name); + + try { + if (constructor == null) { + // Class not found in the cache, see if it's real, and try to add it + Class clazz = mContext.getClassLoader().loadClass( + prefix != null ? (prefix + name) : name); + + if (mFilter != null && clazz != null) { + boolean allowed = mFilter.onLoadClass(clazz); + if (!allowed) { + failNotAllowed(name, prefix, attrs); + } + } + constructor = clazz.getConstructor(mConstructorSignature); + sConstructorMap.put(name, constructor); + } else { + // If we have a filter, apply it to cached constructor + if (mFilter != null) { + // Have we seen this name before? + Boolean allowedState = mFilterMap.get(name); + if (allowedState == null) { + // New class -- remember whether it is allowed + Class clazz = mContext.getClassLoader().loadClass( + prefix != null ? (prefix + name) : name); + + boolean allowed = clazz != null && mFilter.onLoadClass(clazz); + mFilterMap.put(name, allowed); + if (!allowed) { + failNotAllowed(name, prefix, attrs); + } + } else if (allowedState.equals(Boolean.FALSE)) { + failNotAllowed(name, prefix, attrs); + } + } + } + + Object[] args = mConstructorArgs; + args[1] = attrs; + return (View) constructor.newInstance(args); + + } catch (NoSuchMethodException e) { + InflateException ie = new InflateException(attrs.getPositionDescription() + + ": Error inflating class " + + (prefix != null ? (prefix + name) : name)); + ie.initCause(e); + throw ie; + + } catch (ClassNotFoundException e) { + // If loadClass fails, we should propagate the exception. + throw e; + } catch (Exception e) { + InflateException ie = new InflateException(attrs.getPositionDescription() + + ": Error inflating class " + + (constructor == null ? "<unknown>" : constructor.getClass().getName())); + ie.initCause(e); + throw ie; + } + } + + /** + * Throw an excpetion because the specified class is not allowed to be inflated. + */ + private void failNotAllowed(String name, String prefix, AttributeSet attrs) { + InflateException ie = new InflateException(attrs.getPositionDescription() + + ": Class not allowed to be inflated " + + (prefix != null ? (prefix + name) : name)); + throw ie; + } + + /** + * This routine is responsible for creating the correct subclass of View + * given the xml element name. Override it to handle custom view objects. If + * you override this in your subclass be sure to call through to + * super.onCreateView(name) for names you do not recognize. + * + * @param name The fully qualified class name of the View to be create. + * @param attrs An AttributeSet of attributes to apply to the View. + * + * @return View The View created. + */ + protected View onCreateView(String name, AttributeSet attrs) + throws ClassNotFoundException { + return createView(name, "android.view.", attrs); + } + + /* + * default visibility so the BridgeInflater can override it. + */ + View createViewFromTag(String name, AttributeSet attrs) { + if (name.equals("view")) { + name = attrs.getAttributeValue(null, "class"); + } + + if (DEBUG) System.out.println("******** Creating view: " + name); + + try { + View view = (mFactory == null) ? null : mFactory.onCreateView(name, + mContext, attrs); + + if (view == null) { + if (-1 == name.indexOf('.')) { + view = onCreateView(name, attrs); + } else { + view = createView(name, null, attrs); + } + } + + if (DEBUG) System.out.println("Created view is: " + view); + return view; + + } catch (InflateException e) { + throw e; + + } catch (ClassNotFoundException e) { + InflateException ie = new InflateException(attrs.getPositionDescription() + + ": Error inflating class " + name); + ie.initCause(e); + throw ie; + + } catch (Exception e) { + InflateException ie = new InflateException(attrs.getPositionDescription() + + ": Error inflating class " + name); + ie.initCause(e); + throw ie; + } + } + + /** + * Recursive method used to descend down the xml hierarchy and instantiate + * views, instantiate their children, and then call onFinishInflate(). + */ + private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs) + throws XmlPullParserException, IOException { + + final int depth = parser.getDepth(); + int type; + + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + final String name = parser.getName(); + + if (TAG_REQUEST_FOCUS.equals(name)) { + parseRequestFocus(parser, parent); + } else if (TAG_INCLUDE.equals(name)) { + if (parser.getDepth() == 0) { + throw new InflateException("<include /> cannot be the root element"); + } + parseInclude(parser, parent, attrs); + } else if (TAG_MERGE.equals(name)) { + throw new InflateException("<merge /> must be the root element"); + } else { + final View view = createViewFromTag(name, attrs); + final ViewGroup viewGroup = (ViewGroup) parent; + final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); + rInflate(parser, view, attrs); + viewGroup.addView(view, params); + } + } + + parent.onFinishInflate(); + } + + private void parseRequestFocus(XmlPullParser parser, View parent) + throws XmlPullParserException, IOException { + int type; + parent.requestFocus(); + final int currentDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { + // Empty + } + } + + private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs) + throws XmlPullParserException, IOException { + + int type; + + if (parent instanceof ViewGroup) { + final int layout = attrs.getAttributeResourceValue(null, "layout", 0); + if (layout == 0) { + final String value = attrs.getAttributeValue(null, "layout"); + if (value == null) { + throw new InflateException("You must specifiy a layout in the" + + " include tag: <include layout=\"@layout/layoutID\" />"); + } else { + throw new InflateException("You must specifiy a valid layout " + + "reference. The layout ID " + value + " is not valid."); + } + } else { + final XmlResourceParser childParser = + getContext().getResources().getLayout(layout); + + try { + final AttributeSet childAttrs = Xml.asAttributeSet(childParser); + + while ((type = childParser.next()) != XmlPullParser.START_TAG && + type != XmlPullParser.END_DOCUMENT) { + // Empty. + } + + if (type != XmlPullParser.START_TAG) { + throw new InflateException(childParser.getPositionDescription() + + ": No start tag found!"); + } + + final String childName = childParser.getName(); + + if (TAG_MERGE.equals(childName)) { + // Inflate all children. + rInflate(childParser, parent, childAttrs); + } else { + final View view = createViewFromTag(childName, childAttrs); + final ViewGroup group = (ViewGroup) parent; + + // We try to load the layout params set in the <include /> tag. If + // they don't exist, we will rely on the layout params set in the + // included XML file. + // During a layoutparams generation, a runtime exception is thrown + // if either layout_width or layout_height is missing. We catch + // this exception and set localParams accordingly: true means we + // successfully loaded layout params from the <include /> tag, + // false means we need to rely on the included layout params. + ViewGroup.LayoutParams params = null; + try { + params = group.generateLayoutParams(attrs); + } catch (RuntimeException e) { + params = group.generateLayoutParams(childAttrs); + } finally { + if (params != null) { + view.setLayoutParams(params); + } + } + + // Inflate all children. + rInflate(childParser, view, childAttrs); + + // Attempt to override the included layout's android:id with the + // one set on the <include /> tag itself. + TypedArray a = mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.View, 0, 0); + int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID); + // While we're at it, let's try to override android:visibility. + int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1); + a.recycle(); + + if (id != View.NO_ID) { + view.setId(id); + } + + switch (visibility) { + case 0: + view.setVisibility(View.VISIBLE); + break; + case 1: + view.setVisibility(View.INVISIBLE); + break; + case 2: + view.setVisibility(View.GONE); + break; + } + + group.addView(view); + } + } finally { + childParser.close(); + } + } + } else { + throw new InflateException("<include /> can only be used inside of a ViewGroup"); + } + + final int currentDepth = parser.getDepth(); + while (((type = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { + // Empty + } + } +} diff --git a/core/java/android/view/Menu.java b/core/java/android/view/Menu.java new file mode 100644 index 0000000..f2ec076 --- /dev/null +++ b/core/java/android/view/Menu.java @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2006 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.view; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; + +/** + * Interface for managing the items in a menu. + * <p> + * By default, every Activity supports an options menu of actions or options. + * You can add items to this menu and handle clicks on your additions. The + * easiest way of adding menu items is inflating an XML file into the + * {@link Menu} via {@link MenuInflater}. The easiest way of attaching code to + * clicks is via {@link Activity#onOptionsItemSelected(MenuItem)} and + * {@link Activity#onContextItemSelected(MenuItem)}. + * <p> + * Different menu types support different features: + * <ol> + * <li><b>Context menus</b>: Do not support item shortcuts, item icons, and sub + * menus. + * <li><b>Options menus</b>: The <b>icon menus</b> do not support item check + * marks and only show the item's + * {@link MenuItem#setTitleCondensed(CharSequence) condensed title}. The + * <b>expanded menus</b> (only available if six or more menu items are visible, + * reached via the 'More' item in the icon menu) do not show item icons, and + * item check marks are discouraged. + * <li><b>Sub menus</b>: Do not support item icons, or nested sub menus. + * </ol> + */ +public interface Menu { + + /** + * This is the part of an order integer that the user can provide. + * @hide + */ + static final int USER_MASK = 0x0000ffff; + /** + * Bit shift of the user portion of the order integer. + * @hide + */ + static final int USER_SHIFT = 0; + + /** + * This is the part of an order integer that supplies the category of the + * item. + * @hide + */ + static final int CATEGORY_MASK = 0xffff0000; + /** + * Bit shift of the category portion of the order integer. + * @hide + */ + static final int CATEGORY_SHIFT = 16; + + /** + * Value to use for group and item identifier integers when you don't care + * about them. + */ + static final int NONE = 0; + + /** + * First value for group and item identifier integers. + */ + static final int FIRST = 1; + + // Implementation note: Keep these CATEGORY_* in sync with the category enum + // in attrs.xml + + /** + * Category code for the order integer for items/groups that are part of a + * container -- or/add this with your base value. + */ + static final int CATEGORY_CONTAINER = 0x00010000; + + /** + * Category code for the order integer for items/groups that are provided by + * the system -- or/add this with your base value. + */ + static final int CATEGORY_SYSTEM = 0x00020000; + + /** + * Category code for the order integer for items/groups that are + * user-supplied secondary (infrequently used) options -- or/add this with + * your base value. + */ + static final int CATEGORY_SECONDARY = 0x00030000; + + /** + * Category code for the order integer for items/groups that are + * alternative actions on the data that is currently displayed -- or/add + * this with your base value. + */ + static final int CATEGORY_ALTERNATIVE = 0x00040000; + + /** + * Flag for {@link #addIntentOptions}: if set, do not automatically remove + * any existing menu items in the same group. + */ + static final int FLAG_APPEND_TO_GROUP = 0x0001; + + /** + * Flag for {@link #performShortcut}: if set, do not close the menu after + * executing the shortcut. + */ + static final int FLAG_PERFORM_NO_CLOSE = 0x0001; + + /** + * Flag for {@link #performShortcut(int, KeyEvent, int)}: if set, always + * close the menu after executing the shortcut. Closing the menu also resets + * the prepared state. + */ + static final int FLAG_ALWAYS_PERFORM_CLOSE = 0x0002; + + /** + * Add a new item to the menu. This item displays the given title for its + * label. + * + * @param title The text to display for the item. + * @return The newly added menu item. + */ + public MenuItem add(CharSequence title); + + /** + * Add a new item to the menu. This item displays the given title for its + * label. + * + * @param titleRes Resource identifier of title string. + * @return The newly added menu item. + */ + public MenuItem add(int titleRes); + + /** + * Add a new item to the menu. This item displays the given title for its + * label. + * + * @param groupId The group identifier that this item should be part of. + * This can be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a + * group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care + * about the order. See {@link MenuItem#getOrder()}. + * @param title The text to display for the item. + * @return The newly added menu item. + */ + public MenuItem add(int groupId, int itemId, int order, CharSequence title); + + /** + * Variation on {@link #add(int, int, int, CharSequence)} that takes a + * string resource identifier instead of the string itself. + * + * @param groupId The group identifier that this item should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a + * group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care + * about the order. See {@link MenuItem#getOrder()}. + * @param titleRes Resource identifier of title string. + * @return The newly added menu item. + */ + public MenuItem add(int groupId, int itemId, int order, int titleRes); + + /** + * Add a new sub-menu to the menu. This item displays the given title for + * its label. To modify other attributes on the submenu's menu item, use + * {@link SubMenu#getItem()}. + * + * @param title The text to display for the item. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(final CharSequence title); + + /** + * Add a new sub-menu to the menu. This item displays the given title for + * its label. To modify other attributes on the submenu's menu item, use + * {@link SubMenu#getItem()}. + * + * @param titleRes Resource identifier of title string. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(final int titleRes); + + /** + * Add a new sub-menu to the menu. This item displays the given + * <var>title</var> for its label. To modify other attributes on the + * submenu's menu item, use {@link SubMenu#getItem()}. + *<p> + * Note that you can only have one level of sub-menus, i.e. you cannnot add + * a subMenu to a subMenu: An {@link UnsupportedOperationException} will be + * thrown if you try. + * + * @param groupId The group identifier that this item should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a + * group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care + * about the order. See {@link MenuItem#getOrder()}. + * @param title The text to display for the item. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(final int groupId, final int itemId, int order, final CharSequence title); + + /** + * Variation on {@link #addSubMenu(int, int, int, CharSequence)} that takes + * a string resource identifier for the title instead of the string itself. + * + * @param groupId The group identifier that this item should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if an item should not be in a group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a unique ID. + * @param order The order for the item. Use {@link #NONE} if you do not care about the + * order. See {@link MenuItem#getOrder()}. + * @param titleRes Resource identifier of title string. + * @return The newly added sub-menu + */ + SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes); + + /** + * Add a group of menu items corresponding to actions that can be performed + * for a particular Intent. The Intent is most often configured with a null + * action, the data that the current activity is working with, and includes + * either the {@link Intent#CATEGORY_ALTERNATIVE} or + * {@link Intent#CATEGORY_SELECTED_ALTERNATIVE} to find activities that have + * said they would like to be included as optional action. You can, however, + * use any Intent you want. + * + * <p> + * See {@link android.content.pm.PackageManager#queryIntentActivityOptions} + * for more * details on the <var>caller</var>, <var>specifics</var>, and + * <var>intent</var> arguments. The list returned by that function is used + * to populate the resulting menu items. + * + * <p> + * All of the menu items of possible options for the intent will be added + * with the given group and id. You can use the group to control ordering of + * the items in relation to other items in the menu. Normally this function + * will automatically remove any existing items in the menu in the same + * group and place a divider above and below the added items; this behavior + * can be modified with the <var>flags</var> parameter. For each of the + * generated items {@link MenuItem#setIntent} is called to associate the + * appropriate Intent with the item; this means the activity will + * automatically be started for you without having to do anything else. + * + * @param groupId The group identifier that the items should be part of. + * This can also be used to define groups of items for batch state + * changes. Normally use {@link #NONE} if the items should not be in + * a group. + * @param itemId Unique item ID. Use {@link #NONE} if you do not need a + * unique ID. + * @param order The order for the items. Use {@link #NONE} if you do not + * care about the order. See {@link MenuItem#getOrder()}. + * @param caller The current activity component name as defined by + * queryIntentActivityOptions(). + * @param specifics Specific items to place first as defined by + * queryIntentActivityOptions(). + * @param intent Intent describing the kinds of items to populate in the + * list as defined by queryIntentActivityOptions(). + * @param flags Additional options controlling how the items are added. + * @param outSpecificItems Optional array in which to place the menu items + * that were generated for each of the <var>specifics</var> that were + * requested. Entries may be null if no activity was found for that + * specific action. + * @return The number of menu items that were added. + * + * @see #FLAG_APPEND_TO_GROUP + * @see MenuItem#setIntent + * @see android.content.pm.PackageManager#queryIntentActivityOptions + */ + public int addIntentOptions(int groupId, int itemId, int order, + ComponentName caller, Intent[] specifics, + Intent intent, int flags, MenuItem[] outSpecificItems); + + /** + * Remove the item with the given identifier. + * + * @param id The item to be removed. If there is no item with this + * identifier, nothing happens. + */ + public void removeItem(int id); + + /** + * Remove all items in the given group. + * + * @param groupId The group to be removed. If there are no items in this + * group, nothing happens. + */ + public void removeGroup(int groupId); + + /** + * Remove all existing items from the menu, leaving it empty as if it had + * just been created. + */ + public void clear(); + + /** + * Control whether a particular group of items can show a check mark. This + * is similar to calling {@link MenuItem#setCheckable} on all of the menu items + * with the given group identifier, but in addition you can control whether + * this group contains a mutually-exclusive set items. This should be called + * after the items of the group have been added to the menu. + * + * @param group The group of items to operate on. + * @param checkable Set to true to allow a check mark, false to + * disallow. The default is false. + * @param exclusive If set to true, only one item in this group can be + * checked at a time; checking an item will automatically + * uncheck all others in the group. If set to false, each + * item can be checked independently of the others. + * + * @see MenuItem#setCheckable + * @see MenuItem#setChecked + */ + public void setGroupCheckable(int group, boolean checkable, boolean exclusive); + + /** + * Show or hide all menu items that are in the given group. + * + * @param group The group of items to operate on. + * @param visible If true the items are visible, else they are hidden. + * + * @see MenuItem#setVisible + */ + public void setGroupVisible(int group, boolean visible); + + /** + * Enable or disable all menu items that are in the given group. + * + * @param group The group of items to operate on. + * @param enabled If true the items will be enabled, else they will be disabled. + * + * @see MenuItem#setEnabled + */ + public void setGroupEnabled(int group, boolean enabled); + + /** + * Return whether the menu currently has item items that are visible. + * + * @return True if there is one or more item visible, + * else false. + */ + public boolean hasVisibleItems(); + + /** + * Return the menu item with a particular identifier. + * + * @param id The identifier to find. + * + * @return The menu item object, or null if there is no item with + * this identifier. + */ + public MenuItem findItem(int id); + + /** + * Get the number of items in the menu. Note that this will change any + * times items are added or removed from the menu. + * + * @return The item count. + */ + public int size(); + + /** + * Execute the menu item action associated with the given shortcut + * character. + * + * @param keyCode The keycode of the shortcut key. + * @param event Key event message. + * @param flags Additional option flags or 0. + * + * @return If the given shortcut exists and is shown, returns + * true; else returns false. + * + * @see #FLAG_PERFORM_NO_CLOSE + */ + public boolean performShortcut(int keyCode, KeyEvent event, int flags); + + /** + * Is a keypress one of the defined shortcut keys for this window. + * @param keyCode the key code from {@link KeyEvent} to check. + * @param event the {@link KeyEvent} to use to help check. + */ + boolean isShortcutKey(int keyCode, KeyEvent event); + + /** + * Execute the menu item action associated with the given menu identifier. + * + * @param id Identifier associated with the menu item. + * @param flags Additional option flags or 0. + * + * @return If the given identifier exists and is shown, returns + * true; else returns false. + * + * @see #FLAG_PERFORM_NO_CLOSE + */ + public boolean performIdentifierAction(int id, int flags); + + + /** + * Control whether the menu should be running in qwerty mode (alphabetic + * shortcuts) or 12-key mode (numeric shortcuts). + * + * @param isQwerty If true the menu will use alphabetic shortcuts; else it + * will use numeric shortcuts. + */ + public void setQwertyMode(boolean isQwerty); +} + diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java new file mode 100644 index 0000000..46c805c --- /dev/null +++ b/core/java/android/view/MenuInflater.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2006 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.view; + +import com.android.internal.view.menu.MenuItemImpl; + +import java.io.IOException; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Xml; + +/** + * This class is used to instantiate menu XML files into Menu objects. + * <p> + * For performance reasons, menu inflation relies heavily on pre-processing of + * XML files that is done at build time. Therefore, it is not currently possible + * to use MenuInflater with an XmlPullParser over a plain XML file at runtime; + * it only works with an XmlPullParser returned from a compiled resource (R. + * <em>something</em> file.) + */ +public class MenuInflater { + /** Menu tag name in XML. */ + private static final String XML_MENU = "menu"; + + /** Group tag name in XML. */ + private static final String XML_GROUP = "group"; + + /** Item tag name in XML. */ + private static final String XML_ITEM = "item"; + + private static final int NO_ID = 0; + + private Context mContext; + + /** + * Constructs a menu inflater. + * + * @see Activity#getMenuInflater() + */ + public MenuInflater(Context context) { + mContext = context; + } + + /** + * Inflate a menu hierarchy from the specified XML resource. Throws + * {@link InflateException} if there is an error. + * + * @param menuRes Resource ID for an XML layout resource to load (e.g., + * <code>R.menu.main_activity</code>) + * @param menu The Menu to inflate into. The items and submenus will be + * added to this Menu. + */ + public void inflate(int menuRes, Menu menu) { + XmlResourceParser parser = null; + try { + parser = mContext.getResources().getLayout(menuRes); + AttributeSet attrs = Xml.asAttributeSet(parser); + + parseMenu(parser, attrs, menu); + } catch (XmlPullParserException e) { + throw new InflateException("Error inflating menu XML", e); + } catch (IOException e) { + throw new InflateException("Error inflating menu XML", e); + } finally { + if (parser != null) parser.close(); + } + } + + /** + * Called internally to fill the given menu. If a sub menu is seen, it will + * call this recursively. + */ + private void parseMenu(XmlPullParser parser, AttributeSet attrs, Menu menu) + throws XmlPullParserException, IOException { + MenuState menuState = new MenuState(menu); + + int eventType = parser.getEventType(); + String tagName; + boolean lookingForEndOfUnknownTag = false; + String unknownTagName = null; + + // This loop will skip to the menu start tag + do { + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.getName(); + if (tagName.equals(XML_MENU)) { + // Go to next tag + eventType = parser.next(); + break; + } + + throw new RuntimeException("Expecting menu, got " + tagName); + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + + boolean reachedEndOfMenu = false; + while (!reachedEndOfMenu) { + switch (eventType) { + case XmlPullParser.START_TAG: + if (lookingForEndOfUnknownTag) { + break; + } + + tagName = parser.getName(); + if (tagName.equals(XML_GROUP)) { + menuState.readGroup(attrs); + } else if (tagName.equals(XML_ITEM)) { + menuState.readItem(attrs); + } else if (tagName.equals(XML_MENU)) { + // A menu start tag denotes a submenu for an item + SubMenu subMenu = menuState.addSubMenuItem(); + + // Parse the submenu into returned SubMenu + parseMenu(parser, attrs, subMenu); + } else { + lookingForEndOfUnknownTag = true; + unknownTagName = tagName; + } + break; + + case XmlPullParser.END_TAG: + tagName = parser.getName(); + if (lookingForEndOfUnknownTag && tagName.equals(unknownTagName)) { + lookingForEndOfUnknownTag = false; + unknownTagName = null; + } else if (tagName.equals(XML_GROUP)) { + menuState.resetGroup(); + } else if (tagName.equals(XML_ITEM)) { + // Add the item if it hasn't been added (if the item was + // a submenu, it would have been added already) + if (!menuState.hasAddedItem()) { + menuState.addItem(); + } + } else if (tagName.equals(XML_MENU)) { + reachedEndOfMenu = true; + } + break; + + case XmlPullParser.END_DOCUMENT: + throw new RuntimeException("Unexpected end of document"); + } + + eventType = parser.next(); + } + } + + /** + * State for the current menu. + * <p> + * Groups can not be nested unless there is another menu (which will have + * its state class). + */ + private class MenuState { + private Menu menu; + + /* + * Group state is set on items as they are added, allowing an item to + * override its group state. (As opposed to set on items at the group end tag.) + */ + private int groupId; + private int groupCategory; + private int groupOrder; + private int groupCheckable; + private boolean groupVisible; + private boolean groupEnabled; + + private boolean itemAdded; + private int itemId; + private int itemCategoryOrder; + private String itemTitle; + private String itemTitleCondensed; + private int itemIconResId; + private char itemAlphabeticShortcut; + private char itemNumericShortcut; + /** + * Sync to attrs.xml enum: + * - 0: none + * - 1: all + * - 2: exclusive + */ + private int itemCheckable; + private boolean itemChecked; + private boolean itemVisible; + private boolean itemEnabled; + + private static final int defaultGroupId = NO_ID; + private static final int defaultItemId = NO_ID; + private static final int defaultItemCategory = 0; + private static final int defaultItemOrder = 0; + private static final int defaultItemCheckable = 0; + private static final boolean defaultItemChecked = false; + private static final boolean defaultItemVisible = true; + private static final boolean defaultItemEnabled = true; + + public MenuState(final Menu menu) { + this.menu = menu; + + resetGroup(); + } + + public void resetGroup() { + groupId = defaultGroupId; + groupCategory = defaultItemCategory; + groupOrder = defaultItemOrder; + groupCheckable = defaultItemCheckable; + groupVisible = defaultItemVisible; + groupEnabled = defaultItemEnabled; + } + + /** + * Called when the parser is pointing to a group tag. + */ + public void readGroup(AttributeSet attrs) { + TypedArray a = mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.MenuGroup); + + groupId = a.getResourceId(com.android.internal.R.styleable.MenuGroup_id, defaultGroupId); + groupCategory = a.getInt(com.android.internal.R.styleable.MenuGroup_menuCategory, defaultItemCategory); + groupOrder = a.getInt(com.android.internal.R.styleable.MenuGroup_orderInCategory, defaultItemOrder); + groupCheckable = a.getInt(com.android.internal.R.styleable.MenuGroup_checkableBehavior, defaultItemCheckable); + groupVisible = a.getBoolean(com.android.internal.R.styleable.MenuGroup_visible, defaultItemVisible); + groupEnabled = a.getBoolean(com.android.internal.R.styleable.MenuGroup_enabled, defaultItemEnabled); + + a.recycle(); + } + + /** + * Called when the parser is pointing to an item tag. + */ + public void readItem(AttributeSet attrs) { + TypedArray a = mContext.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.MenuItem); + + // Inherit attributes from the group as default value + itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId); + final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory); + final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder); + itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK); + itemTitle = a.getString(com.android.internal.R.styleable.MenuItem_title); + itemTitleCondensed = a.getString(com.android.internal.R.styleable.MenuItem_titleCondensed); + itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0); + itemAlphabeticShortcut = + getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut)); + itemNumericShortcut = + getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut)); + if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) { + // Item has attribute checkable, use it + itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0; + } else { + // Item does not have attribute, use the group's (group can have one more state + // for checkable that represents the exclusive checkable) + itemCheckable = groupCheckable; + } + itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked); + itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible); + itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled); + + a.recycle(); + + itemAdded = false; + } + + private char getShortcut(String shortcutString) { + if (shortcutString == null) { + return 0; + } else { + return shortcutString.charAt(0); + } + } + + private void setItem(MenuItem item) { + item.setChecked(itemChecked) + .setVisible(itemVisible) + .setEnabled(itemEnabled) + .setCheckable(itemCheckable >= 1) + .setTitleCondensed(itemTitleCondensed) + .setIcon(itemIconResId) + .setAlphabeticShortcut(itemAlphabeticShortcut) + .setNumericShortcut(itemNumericShortcut); + + if (itemCheckable >= 2) { + ((MenuItemImpl) item).setExclusiveCheckable(true); + } + } + + public void addItem() { + itemAdded = true; + setItem(menu.add(groupId, itemId, itemCategoryOrder, itemTitle)); + } + + public SubMenu addSubMenuItem() { + itemAdded = true; + SubMenu subMenu = menu.addSubMenu(groupId, itemId, itemCategoryOrder, itemTitle); + setItem(subMenu.getItem()); + return subMenu; + } + + public boolean hasAddedItem() { + return itemAdded; + } + } + +} diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java new file mode 100644 index 0000000..92cf4af --- /dev/null +++ b/core/java/android/view/MenuItem.java @@ -0,0 +1,368 @@ +package android.view; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View.OnCreateContextMenuListener; + +/** + * Interface for direct access to a previously created menu item. + * <p> + * An Item is returned by calling one of the {@link android.view.Menu#add} + * methods. + * <p> + * For a feature set of specific menu types, see {@link Menu}. + */ +public interface MenuItem { + /** + * Interface definition for a callback to be invoked when a menu item is + * clicked. + * + * @see Activity#onContextItemSelected(MenuItem) + * @see Activity#onOptionsItemSelected(MenuItem) + */ + public interface OnMenuItemClickListener { + /** + * Called when a menu item has been invoked. This is the first code + * that is executed; if it returns true, no other callbacks will be + * executed. + * + * @param item The menu item that was invoked. + * + * @return Return true to consume this click and prevent others from + * executing. + */ + public boolean onMenuItemClick(MenuItem item); + } + + /** + * Return the identifier for this menu item. The identifier can not + * be changed after the menu is created. + * + * @return The menu item's identifier. + */ + public int getItemId(); + + /** + * Return the group identifier that this menu item is part of. The group + * identifier can not be changed after the menu is created. + * + * @return The menu item's group identifier. + */ + public int getGroupId(); + + /** + * Return the category and order within the category of this item. This + * item will be shown before all items (within its category) that have + * order greater than this value. + * <p> + * An order integer contains the item's category (the upper bits of the + * integer; set by or/add the category with the order within the + * category) and the ordering of the item within that category (the + * lower bits). Example categories are {@link Menu#CATEGORY_SYSTEM}, + * {@link Menu#CATEGORY_SECONDARY}, {@link Menu#CATEGORY_ALTERNATIVE}, + * {@link Menu#CATEGORY_CONTAINER}. See {@link Menu} for a full list. + * + * @return The order of this item. + */ + public int getOrder(); + + /** + * Change the title associated with this item. + * + * @param title The new text to be displayed. + * @return This Item so additional setters can be called. + */ + public MenuItem setTitle(CharSequence title); + + /** + * Change the title associated with this item. + * <p> + * Some menu types do not sufficient space to show the full title, and + * instead a condensed title is preferred. See {@link Menu} for more + * information. + * + * @param title The resource id of the new text to be displayed. + * @return This Item so additional setters can be called. + * @see #setTitleCondensed(CharSequence) + */ + + public MenuItem setTitle(int title); + + /** + * Retrieve the current title of the item. + * + * @return The title. + */ + public CharSequence getTitle(); + + /** + * Change the condensed title associated with this item. The condensed + * title is used in situations where the normal title may be too long to + * be displayed. + * + * @param title The new text to be displayed as the condensed title. + * @return This Item so additional setters can be called. + */ + public MenuItem setTitleCondensed(CharSequence title); + + /** + * Retrieve the current condensed title of the item. If a condensed + * title was never set, it will return the normal title. + * + * @return The condensed title, if it exists. + * Otherwise the normal title. + */ + public CharSequence getTitleCondensed(); + + /** + * Change the icon associated with this item. This icon will not always be + * shown, so the title should be sufficient in describing this item. See + * {@link Menu} for the menu types that support icons. + * + * @param icon The new icon (as a Drawable) to be displayed. + * @return This Item so additional setters can be called. + */ + public MenuItem setIcon(Drawable icon); + + /** + * Change the icon associated with this item. This icon will not always be + * shown, so the title should be sufficient in describing this item. See + * {@link Menu} for the menu types that support icons. + * <p> + * This method will set the resource ID of the icon which will be used to + * lazily get the Drawable when this item is being shown. + * + * @param iconRes The new icon (as a resource ID) to be displayed. + * @return This Item so additional setters can be called. + */ + public MenuItem setIcon(int iconRes); + + /** + * Returns the icon for this item as a Drawable (getting it from resources if it hasn't been + * loaded before). + * + * @return The icon as a Drawable. + */ + public Drawable getIcon(); + + /** + * Change the Intent associated with this item. By default there is no + * Intent associated with a menu item. If you set one, and nothing + * else handles the item, then the default behavior will be to call + * {@link android.content.Context#startActivity} with the given Intent. + * + * <p>Note that setIntent() can not be used with the versions of + * {@link Menu#add} that take a Runnable, because {@link Runnable#run} + * does not return a value so there is no way to tell if it handled the + * item. In this case it is assumed that the Runnable always handles + * the item, and the intent will never be started. + * + * @see #getIntent + * @param intent The Intent to associated with the item. This Intent + * object is <em>not</em> copied, so be careful not to + * modify it later. + * @return This Item so additional setters can be called. + */ + public MenuItem setIntent(Intent intent); + + /** + * Return the Intent associated with this item. This returns a + * reference to the Intent which you can change as desired to modify + * what the Item is holding. + * + * @see #setIntent + * @return Returns the last value supplied to {@link #setIntent}, or + * null. + */ + public Intent getIntent(); + + /** + * Change both the numeric and alphabetic shortcut associated with this + * item. Note that the shortcut will be triggered when the key that + * generates the given character is pressed alone or along with with the alt + * key. Also note that case is not significant and that alphabetic shortcut + * characters will be displayed in lower case. + * <p> + * See {@link Menu} for the menu types that support shortcuts. + * + * @param numericChar The numeric shortcut key. This is the shortcut when + * using a numeric (e.g., 12-key) keyboard. + * @param alphaChar The alphabetic shortcut key. This is the shortcut when + * using a keyboard with alphabetic keys. + * @return This Item so additional setters can be called. + */ + public MenuItem setShortcut(char numericChar, char alphaChar); + + /** + * Change the numeric shortcut associated with this item. + * <p> + * See {@link Menu} for the menu types that support shortcuts. + * + * @param numericChar The numeric shortcut key. This is the shortcut when + * using a 12-key (numeric) keyboard. + * @return This Item so additional setters can be called. + */ + public MenuItem setNumericShortcut(char numericChar); + + /** + * Return the char for this menu item's numeric (12-key) shortcut. + * + * @return Numeric character to use as a shortcut. + */ + public char getNumericShortcut(); + + /** + * Change the alphabetic shortcut associated with this item. The shortcut + * will be triggered when the key that generates the given character is + * pressed alone or along with with the alt key. Case is not significant and + * shortcut characters will be displayed in lower case. Note that menu items + * with the characters '\b' or '\n' as shortcuts will get triggered by the + * Delete key or Carriage Return key, respectively. + * <p> + * See {@link Menu} for the menu types that support shortcuts. + * + * @param alphaChar The alphabetic shortcut key. This is the shortcut when + * using a keyboard with alphabetic keys. + * @return This Item so additional setters can be called. + */ + public MenuItem setAlphabeticShortcut(char alphaChar); + + /** + * Return the char for this menu item's alphabetic shortcut. + * + * @return Alphabetic character to use as a shortcut. + */ + public char getAlphabeticShortcut(); + + /** + * Control whether this item can display a check mark. Setting this does + * not actually display a check mark (see {@link #setChecked} for that); + * rather, it ensures there is room in the item in which to display a + * check mark. + * <p> + * See {@link Menu} for the menu types that support check marks. + * + * @param checkable Set to true to allow a check mark, false to + * disallow. The default is false. + * @see #setChecked + * @see #isCheckable + * @see Menu#setGroupCheckable + * @return This Item so additional setters can be called. + */ + public MenuItem setCheckable(boolean checkable); + + /** + * Return whether the item can currently display a check mark. + * + * @return If a check mark can be displayed, returns true. + * + * @see #setCheckable + */ + public boolean isCheckable(); + + /** + * Control whether this item is shown with a check mark. Note that you + * must first have enabled checking with {@link #setCheckable} or else + * the check mark will not appear. If this item is a member of a group that contains + * mutually-exclusive items (set via {@link Menu#setGroupCheckable(int, boolean, boolean)}, + * the other items in the group will be unchecked. + * <p> + * See {@link Menu} for the menu types that support check marks. + * + * @see #setCheckable + * @see #isChecked + * @see Menu#setGroupCheckable + * @param checked Set to true to display a check mark, false to hide + * it. The default value is false. + * @return This Item so additional setters can be called. + */ + public MenuItem setChecked(boolean checked); + + /** + * Return whether the item is currently displaying a check mark. + * + * @return If a check mark is displayed, returns true. + * + * @see #setChecked + */ + public boolean isChecked(); + + /** + * Sets the visibility of the menu item. Even if a menu item is not visible, + * it may still be invoked via its shortcut (to completely disable an item, + * set it to invisible and {@link #setEnabled(boolean) disabled}). + * + * @param visible If true then the item will be visible; if false it is + * hidden. + * @return This Item so additional setters can be called. + */ + public MenuItem setVisible(boolean visible); + + /** + * Return the visibility of the menu item. + * + * @return If true the item is visible; else it is hidden. + */ + public boolean isVisible(); + + /** + * Sets whether the menu item is enabled. Disabling a menu item will not + * allow it to be invoked via its shortcut. The menu item will still be + * visible. + * + * @param enabled If true then the item will be invokable; if false it is + * won't be invokable. + * @return This Item so additional setters can be called. + */ + public MenuItem setEnabled(boolean enabled); + + /** + * Return the enabled state of the menu item. + * + * @return If true the item is enabled and hence invokable; else it is not. + */ + public boolean isEnabled(); + + /** + * Check whether this item has an associated sub-menu. I.e. it is a + * sub-menu of another menu. + * + * @return If true this item has a menu; else it is a + * normal item. + */ + public boolean hasSubMenu(); + + /** + * Get the sub-menu to be invoked when this item is selected, if it has + * one. See {@link #hasSubMenu()}. + * + * @return The associated menu if there is one, else null + */ + public SubMenu getSubMenu(); + + /** + * Set a custom listener for invocation of this menu item. In most + * situations, it is more efficient and easier to use + * {@link Activity#onOptionsItemSelected(MenuItem)} or + * {@link Activity#onContextItemSelected(MenuItem)}. + * + * @param menuItemClickListener The object to receive invokations. + * @return This Item so additional setters can be called. + * @see Activity#onOptionsItemSelected(MenuItem) + * @see Activity#onContextItemSelected(MenuItem) + */ + public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener); + + /** + * Gets the extra information linked to this menu item. This extra + * information is set by the View that added this menu item to the + * menu. + * + * @see OnCreateContextMenuListener + * @return The extra information linked to the View that added this + * menu item to the menu. This can be null. + */ + public ContextMenuInfo getMenuInfo(); +}
\ No newline at end of file diff --git a/core/java/android/view/MotionEvent.aidl b/core/java/android/view/MotionEvent.aidl new file mode 100644 index 0000000..3c89988 --- /dev/null +++ b/core/java/android/view/MotionEvent.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android.view.KeyEvent.aidl +** +** Copyright 2007, 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.view; + +parcelable MotionEvent; diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java new file mode 100644 index 0000000..ab05e16 --- /dev/null +++ b/core/java/android/view/MotionEvent.java @@ -0,0 +1,659 @@ +/* + * Copyright (C) 2007 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.view; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.Config; + +/** + * Object used to report movement (mouse, pen, finger, trackball) events. This + * class may hold either absolute or relative movements, depending on what + * it is being used for. + */ +public final class MotionEvent implements Parcelable { + public static final int ACTION_DOWN = 0; + public static final int ACTION_UP = 1; + public static final int ACTION_MOVE = 2; + public static final int ACTION_CANCEL = 3; + + private static final boolean TRACK_RECYCLED_LOCATION = false; + + /** + * Flag indicating the motion event intersected the top edge of the screen. + */ + public static final int EDGE_TOP = 0x00000001; + + /** + * Flag indicating the motion event intersected the bottom edge of the screen. + */ + public static final int EDGE_BOTTOM = 0x00000002; + + /** + * Flag indicating the motion event intersected the left edge of the screen. + */ + public static final int EDGE_LEFT = 0x00000004; + + /** + * Flag indicating the motion event intersected the right edge of the screen. + */ + public static final int EDGE_RIGHT = 0x00000008; + + static private final int MAX_RECYCLED = 10; + static private Object gRecyclerLock = new Object(); + static private int gRecyclerUsed = 0; + static private MotionEvent gRecyclerTop = null; + + private long mDownTime; + private long mEventTime; + private int mAction; + private float mX; + private float mY; + private float mRawX; + private float mRawY; + private float mPressure; + private float mSize; + private int mMetaState; + private int mNumHistory; + private float[] mHistory; + private long[] mHistoryTimes; + private float mXPrecision; + private float mYPrecision; + private int mDeviceId; + private int mEdgeFlags; + + private MotionEvent mNext; + private RuntimeException mRecycledLocation; + private boolean mRecycled; + + private MotionEvent() { + } + + static private MotionEvent obtain() { + synchronized (gRecyclerLock) { + if (gRecyclerTop == null) { + return new MotionEvent(); + } + MotionEvent ev = gRecyclerTop; + gRecyclerTop = ev.mNext; + gRecyclerUsed--; + ev.mRecycledLocation = null; + ev.mRecycled = false; + return ev; + } + } + + /** + * Create a new MotionEvent, filling in all of the basic values that + * define the motion. + * + * @param downTime The time (in ms) when the user originally pressed down to start + * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}. + * @param eventTime The the time (in ms) when this specific event was generated. This + * must be obtained from {@link SystemClock#uptimeMillis()}. + * @param action The kind of action being performed -- one of either + * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or + * {@link #ACTION_CANCEL}. + * @param x The X coordinate of this event. + * @param y The Y coordinate of this event. + * @param pressure The current pressure of this event. The pressure generally + * ranges from 0 (no pressure at all) to 1 (normal pressure), however + * values higher than 1 may be generated depending on the calibration of + * the input device. + * @param size A scaled value of the approximate size of the area being pressed when + * touched with the finger. The actual value in pixels corresponding to the finger + * touch is normalized with a device specific range of values + * and scaled to a value between 0 and 1. + * @param metaState The state of any meta / modifier keys that were in effect when + * the event was generated. + * @param xPrecision The precision of the X coordinate being reported. + * @param yPrecision The precision of the Y coordinate being reported. + * @param deviceId The id for the device that this event came from. An id of + * zero indicates that the event didn't come from a physical device; other + * numbers are arbitrary and you shouldn't depend on the values. + * @param edgeFlags A bitfield indicating which edges, if any, where touched by this + * MotionEvent. + */ + static public MotionEvent obtain(long downTime, long eventTime, int action, + float x, float y, float pressure, float size, int metaState, + float xPrecision, float yPrecision, int deviceId, int edgeFlags) { + MotionEvent ev = obtain(); + ev.mDeviceId = deviceId; + ev.mEdgeFlags = edgeFlags; + ev.mDownTime = downTime; + ev.mEventTime = eventTime; + ev.mAction = action; + ev.mX = ev.mRawX = x; + ev.mY = ev.mRawY = y; + ev.mPressure = pressure; + ev.mSize = size; + ev.mMetaState = metaState; + ev.mXPrecision = xPrecision; + ev.mYPrecision = yPrecision; + + return ev; + } + + /** + * Create a new MotionEvent, filling in a subset of the basic motion + * values. Those not specified here are: device id (always 0), pressure + * and size (always 1), x and y precision (always 1), and edgeFlags (always 0). + * + * @param downTime The time (in ms) when the user originally pressed down to start + * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}. + * @param eventTime The the time (in ms) when this specific event was generated. This + * must be obtained from {@link SystemClock#uptimeMillis()}. + * @param action The kind of action being performed -- one of either + * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or + * {@link #ACTION_CANCEL}. + * @param x The X coordinate of this event. + * @param y The Y coordinate of this event. + * @param metaState The state of any meta / modifier keys that were in effect when + * the event was generated. + */ + static public MotionEvent obtain(long downTime, long eventTime, int action, + float x, float y, int metaState) { + MotionEvent ev = obtain(); + ev.mDeviceId = 0; + ev.mEdgeFlags = 0; + ev.mDownTime = downTime; + ev.mEventTime = eventTime; + ev.mAction = action; + ev.mX = ev.mRawX = x; + ev.mY = ev.mRawY = y; + ev.mPressure = 1.0f; + ev.mSize = 1.0f; + ev.mMetaState = metaState; + ev.mXPrecision = 1.0f; + ev.mYPrecision = 1.0f; + + return ev; + } + + /** + * Create a new MotionEvent, copying from an existing one. + */ + static public MotionEvent obtain(MotionEvent o) { + MotionEvent ev = obtain(); + ev.mDeviceId = o.mDeviceId; + ev.mEdgeFlags = o.mEdgeFlags; + ev.mDownTime = o.mDownTime; + ev.mEventTime = o.mEventTime; + ev.mAction = o.mAction; + ev.mX = o.mX; + ev.mRawX = o.mRawX; + ev.mY = o.mY; + ev.mRawY = o.mRawY; + ev.mPressure = o.mPressure; + ev.mSize = o.mSize; + ev.mMetaState = o.mMetaState; + ev.mXPrecision = o.mXPrecision; + ev.mYPrecision = o.mYPrecision; + final int N = o.mNumHistory; + ev.mNumHistory = N; + if (N > 0) { + // could be more efficient about this... + ev.mHistory = (float[])o.mHistory.clone(); + ev.mHistoryTimes = (long[])o.mHistoryTimes.clone(); + } + return ev; + } + + /** + * Recycle the MotionEvent, to be re-used by a later caller. After calling + * this function you must not ever touch the event again. + */ + public void recycle() { + // Ensure recycle is only called once! + if (TRACK_RECYCLED_LOCATION) { + if (mRecycledLocation != null) { + throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); + } + mRecycledLocation = new RuntimeException("Last recycled here"); + } else if (mRecycled) { + throw new RuntimeException(toString() + " recycled twice!"); + } + + //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation); + synchronized (gRecyclerLock) { + if (gRecyclerUsed < MAX_RECYCLED) { + gRecyclerUsed++; + mNumHistory = 0; + mNext = gRecyclerTop; + gRecyclerTop = this; + } + } + } + + /** + * Return the kind of action being performed -- one of either + * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or + * {@link #ACTION_CANCEL}. + */ + public final int getAction() { + return mAction; + } + + /** + * Returns the time (in ms) when the user originally pressed down to start + * a stream of position events. + */ + public final long getDownTime() { + return mDownTime; + } + + /** + * Returns the time (in ms) when this specific event was generated. + */ + public final long getEventTime() { + return mEventTime; + } + + /** + * Returns the X coordinate of this event. Whole numbers are pixels; the + * value may have a fraction for input devices that are sub-pixel precise. + */ + public final float getX() { + return mX; + } + + /** + * Returns the Y coordinate of this event. Whole numbers are pixels; the + * value may have a fraction for input devices that are sub-pixel precise. + */ + public final float getY() { + return mY; + } + + /** + * Returns the current pressure of this event. The pressure generally + * ranges from 0 (no pressure at all) to 1 (normal pressure), however + * values higher than 1 may be generated depending on the calibration of + * the input device. + */ + public final float getPressure() { + return mPressure; + } + + /** + * Returns a scaled value of the approximate size, of the area being pressed when + * touched with the finger. The actual value in pixels corresponding to the finger + * touch is normalized with the device specific range of values + * and scaled to a value between 0 and 1. The value of size can be used to + * determine fat touch events. + */ + public final float getSize() { + return mSize; + } + + /** + * Returns the state of any meta / modifier keys that were in effect when + * the event was generated. This is the same values as those + * returned by {@link KeyEvent#getMetaState() KeyEvent.getMetaState}. + * + * @return an integer in which each bit set to 1 represents a pressed + * meta key + * + * @see KeyEvent#getMetaState() + */ + public final int getMetaState() { + return mMetaState; + } + + /** + * Returns the original raw X coordinate of this event. For touch + * events on the screen, this is the original location of the event + * on the screen, before it had been adjusted for the containing window + * and views. + */ + public final float getRawX() { + return mRawX; + } + + /** + * Returns the original raw Y coordinate of this event. For touch + * events on the screen, this is the original location of the event + * on the screen, before it had been adjusted for the containing window + * and views. + */ + public final float getRawY() { + return mRawY; + } + + /** + * Return the precision of the X coordinates being reported. You can + * multiple this number with {@link #getX} to find the actual hardware + * value of the X coordinate. + * @return Returns the precision of X coordinates being reported. + */ + public final float getXPrecision() { + return mXPrecision; + } + + /** + * Return the precision of the Y coordinates being reported. You can + * multiple this number with {@link #getY} to find the actual hardware + * value of the Y coordinate. + * @return Returns the precision of Y coordinates being reported. + */ + public final float getYPrecision() { + return mYPrecision; + } + + /** + * Returns the number of historical points in this event. These are + * movements that have occurred between this event and the previous event. + * This only applies to ACTION_MOVE events -- all other actions will have + * a size of 0. + * + * @return Returns the number of historical points in the event. + */ + public final int getHistorySize() { + return mNumHistory; + } + + /** + * Returns the time that a historical movement occurred between this event + * and the previous event. Only applies to ACTION_MOVE events. + * + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getEventTime + */ + public final long getHistoricalEventTime(int pos) { + return mHistoryTimes[pos]; + } + + /** + * Returns a historical X coordinate that occurred between this event + * and the previous event. Only applies to ACTION_MOVE events. + * + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getX + */ + public final float getHistoricalX(int pos) { + return mHistory[pos*4]; + } + + /** + * Returns a historical Y coordinate that occurred between this event + * and the previous event. Only applies to ACTION_MOVE events. + * + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getY + */ + public final float getHistoricalY(int pos) { + return mHistory[pos*4 + 1]; + } + + /** + * Returns a historical pressure coordinate that occurred between this event + * and the previous event. Only applies to ACTION_MOVE events. + * + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getPressure + */ + public final float getHistoricalPressure(int pos) { + return mHistory[pos*4 + 2]; + } + + /** + * Returns a historical size coordinate that occurred between this event + * and the previous event. Only applies to ACTION_MOVE events. + * + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getSize + */ + public final float getHistoricalSize(int pos) { + return mHistory[pos*4 + 3]; + } + + /** + * Return the id for the device that this event came from. An id of + * zero indicates that the event didn't come from a physical device; other + * numbers are arbitrary and you shouldn't depend on the values. + */ + public final int getDeviceId() { + return mDeviceId; + } + + /** + * Returns a bitfield indicating which edges, if any, where touched by this + * MotionEvent. For touch events, clients can use this to determine if the + * user's finger was touching the edge of the display. + * + * @see #EDGE_LEFT + * @see #EDGE_TOP + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public final int getEdgeFlags() { + return mEdgeFlags; + } + + + /** + * Sets the bitfield indicating which edges, if any, where touched by this + * MotionEvent. + * + * @see #getEdgeFlags() + */ + public final void setEdgeFlags(int flags) { + mEdgeFlags = flags; + } + + /** + * Sets this event's action. + */ + public final void setAction(int action) { + mAction = action; + } + + /** + * Adjust this event's location. + * @param deltaX Amount to add to the current X coordinate of the event. + * @param deltaY Amount to add to the current Y coordinate of the event. + */ + public final void offsetLocation(float deltaX, float deltaY) { + mX += deltaX; + mY += deltaY; + final int N = mNumHistory*4; + if (N <= 0) { + return; + } + final float[] pos = mHistory; + for (int i=0; i<N; i+=4) { + pos[i] += deltaX; + pos[i+1] += deltaY; + } + } + + /** + * Set this event's location. Applies {@link #offsetLocation} with a + * delta from the current location to the given new location. + * + * @param x New absolute X location. + * @param y New absolute Y location. + */ + public final void setLocation(float x, float y) { + float deltaX = x-mX; + float deltaY = y-mY; + if (deltaX != 0 || deltaY != 0) { + offsetLocation(deltaX, deltaY); + } + } + + /** + * Add a new movement to the batch of movements in this event. The event's + * current location, position and size is updated to the new values. In + * the future, the current values in the event will be added to a list of + * historic values. + * + * @param x The new X position. + * @param y The new Y position. + * @param pressure The new pressure. + * @param size The new size. + */ + public final void addBatch(long eventTime, float x, float y, + float pressure, float size, int metaState) { + float[] history = mHistory; + long[] historyTimes = mHistoryTimes; + int N; + int avail; + if (history == null) { + mHistory = history = new float[8*4]; + mHistoryTimes = historyTimes = new long[8]; + mNumHistory = N = 0; + avail = 8; + } else { + N = mNumHistory; + avail = history.length/4; + if (N == avail) { + avail += 8; + float[] newHistory = new float[avail*4]; + System.arraycopy(history, 0, newHistory, 0, N*4); + mHistory = history = newHistory; + long[] newHistoryTimes = new long[avail]; + System.arraycopy(historyTimes, 0, newHistoryTimes, 0, N); + mHistoryTimes = historyTimes = newHistoryTimes; + } + } + + historyTimes[N] = mEventTime; + + final int pos = N*4; + history[pos] = mX; + history[pos+1] = mY; + history[pos+2] = mPressure; + history[pos+3] = mSize; + mNumHistory = N+1; + + mEventTime = eventTime; + mX = mRawX = x; + mY = mRawY = y; + mPressure = pressure; + mSize = size; + mMetaState |= metaState; + } + + @Override + public String toString() { + return "MotionEvent{" + Integer.toHexString(System.identityHashCode(this)) + + " action=" + mAction + " x=" + mX + + " y=" + mY + " pressure=" + mPressure + " size=" + mSize + "}"; + } + + public static final Parcelable.Creator<MotionEvent> CREATOR + = new Parcelable.Creator<MotionEvent>() { + public MotionEvent createFromParcel(Parcel in) { + MotionEvent ev = obtain(); + ev.readFromParcel(in); + return ev; + } + + public MotionEvent[] newArray(int size) { + return new MotionEvent[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mDownTime); + out.writeLong(mEventTime); + out.writeInt(mAction); + out.writeFloat(mX); + out.writeFloat(mY); + out.writeFloat(mPressure); + out.writeFloat(mSize); + out.writeInt(mMetaState); + out.writeFloat(mRawX); + out.writeFloat(mRawY); + final int N = mNumHistory; + out.writeInt(N); + if (N > 0) { + final int N4 = N*4; + int i; + float[] history = mHistory; + for (i=0; i<N4; i++) { + out.writeFloat(history[i]); + } + long[] times = mHistoryTimes; + for (i=0; i<N; i++) { + out.writeLong(times[i]); + } + } + out.writeFloat(mXPrecision); + out.writeFloat(mYPrecision); + out.writeInt(mDeviceId); + out.writeInt(mEdgeFlags); + } + + private void readFromParcel(Parcel in) { + mDownTime = in.readLong(); + mEventTime = in.readLong(); + mAction = in.readInt(); + mX = in.readFloat(); + mY = in.readFloat(); + mPressure = in.readFloat(); + mSize = in.readFloat(); + mMetaState = in.readInt(); + mRawX = in.readFloat(); + mRawY = in.readFloat(); + final int N = in.readInt(); + if ((mNumHistory=N) > 0) { + final int N4 = N*4; + float[] history = mHistory; + if (history == null || history.length < N4) { + mHistory = history = new float[N4 + (4*4)]; + } + for (int i=0; i<N4; i++) { + history[i] = in.readFloat(); + } + long[] times = mHistoryTimes; + if (times == null || times.length < N) { + mHistoryTimes = times = new long[N + 4]; + } + for (int i=0; i<N; i++) { + times[i] = in.readLong(); + } + } + mXPrecision = in.readFloat(); + mYPrecision = in.readFloat(); + mDeviceId = in.readInt(); + mEdgeFlags = in.readInt(); + } + +} + diff --git a/core/java/android/view/OrientationListener.java b/core/java/android/view/OrientationListener.java new file mode 100644 index 0000000..0add025 --- /dev/null +++ b/core/java/android/view/OrientationListener.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008 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.view; + +import android.content.Context; +import android.hardware.SensorListener; +import android.hardware.SensorManager; +import android.util.Config; +import android.util.Log; + +/** + * Helper class for receiving notifications from the SensorManager when + * the orientation of the device has changed. + */ +public abstract class OrientationListener implements SensorListener { + + private static final String TAG = "OrientationListener"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private SensorManager mSensorManager; + private int mOrientation = ORIENTATION_UNKNOWN; + private boolean mEnabled = false; + + /** + * Returned from onOrientationChanged when the device orientation cannot be determined + * (typically when the device is in a close to flat position). + * + * @see #onOrientationChanged + */ + public static final int ORIENTATION_UNKNOWN = -1; + + /** + * Creates a new OrientationListener. + * + * @param context for the OrientationListener. + */ + public OrientationListener(Context context) { + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + } + + /** + * Enables the OrientationListener so it will monitor the sensor and call + * {@link #onOrientationChanged} when the device orientation changes. + */ + public void enable() { + if (mEnabled == false) { + if (localLOGV) Log.d(TAG, "OrientationListener enabled"); + mSensorManager.registerListener(this, SensorManager.SENSOR_ACCELEROMETER); + mEnabled = true; + } + } + + /** + * Disables the OrientationListener. + */ + public void disable() { + if (mEnabled == true) { + if (localLOGV) Log.d(TAG, "OrientationListener disabled"); + mSensorManager.unregisterListener(this); + mEnabled = false; + } + } + + /** + * + */ + public void onSensorChanged(int sensor, float[] values) { + int orientation = ORIENTATION_UNKNOWN; + float X = values[SensorManager.RAW_DATA_X]; + float Y = values[SensorManager.RAW_DATA_Y]; + float Z = values[SensorManager.RAW_DATA_Z]; + float magnitude = X*X + Y*Y; + // Don't trust the angle if the magnitude is small compared to the y value + if (magnitude * 4 >= Z*Z) { + float OneEightyOverPi = 57.29577957855f; + float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi; + orientation = 90 - (int)Math.round(angle); + // normalize to 0 - 359 range + while (orientation >= 360) { + orientation -= 360; + } + while (orientation < 0) { + orientation += 360; + } + } + + if (orientation != mOrientation) { + mOrientation = orientation; + onOrientationChanged(orientation); + } + } + + public void onAccuracyChanged(int sensor, int accuracy) { + // TODO Auto-generated method stub + + } + + /** + * Called when the orientation of the device has changed. + * orientation parameter is in degrees, ranging from 0 to 359. + * orientation is 0 degrees when the device is oriented in its natural position, + * 90 degrees when its left side is at the top, 180 degrees when it is upside down, + * and 270 degrees when its right side is to the top. + * {@link #ORIENTATION_UNKNOWN} is returned when the device is close to flat + * and the orientation cannot be determined. + * + * @param orientation The new orientation of the device. + * + * @see #ORIENTATION_UNKNOWN + */ + abstract public void onOrientationChanged(int orientation); +} diff --git a/core/java/android/view/RawInputEvent.java b/core/java/android/view/RawInputEvent.java new file mode 100644 index 0000000..580a80d --- /dev/null +++ b/core/java/android/view/RawInputEvent.java @@ -0,0 +1,169 @@ +/** + * + */ +package android.view; + +/** + * @hide + * This really belongs in services.jar; WindowManagerPolicy should go there too. + */ +public class RawInputEvent { + // Event class as defined by EventHub. + public static final int CLASS_KEYBOARD = 0x00000001; + public static final int CLASS_TOUCHSCREEN = 0x00000002; + public static final int CLASS_TRACKBALL = 0x00000004; + + // More special classes for QueuedEvent below. + public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000; + + // Event types. + + public static final int EV_SYN = 0x00; + public static final int EV_KEY = 0x01; + public static final int EV_REL = 0x02; + public static final int EV_ABS = 0x03; + public static final int EV_MSC = 0x04; + public static final int EV_SW = 0x05; + public static final int EV_LED = 0x11; + public static final int EV_SND = 0x12; + public static final int EV_REP = 0x14; + public static final int EV_FF = 0x15; + public static final int EV_PWR = 0x16; + public static final int EV_FF_STATUS = 0x17; + + // Platform-specific event types. + + public static final int EV_DEVICE_ADDED = 0x10000000; + public static final int EV_DEVICE_REMOVED = 0x20000000; + + // Special key (EV_KEY) scan codes for pointer buttons. + + public static final int BTN_FIRST = 0x100; + + public static final int BTN_MISC = 0x100; + public static final int BTN_0 = 0x100; + public static final int BTN_1 = 0x101; + public static final int BTN_2 = 0x102; + public static final int BTN_3 = 0x103; + public static final int BTN_4 = 0x104; + public static final int BTN_5 = 0x105; + public static final int BTN_6 = 0x106; + public static final int BTN_7 = 0x107; + public static final int BTN_8 = 0x108; + public static final int BTN_9 = 0x109; + + public static final int BTN_MOUSE = 0x110; + public static final int BTN_LEFT = 0x110; + public static final int BTN_RIGHT = 0x111; + public static final int BTN_MIDDLE = 0x112; + public static final int BTN_SIDE = 0x113; + public static final int BTN_EXTRA = 0x114; + public static final int BTN_FORWARD = 0x115; + public static final int BTN_BACK = 0x116; + public static final int BTN_TASK = 0x117; + + public static final int BTN_JOYSTICK = 0x120; + public static final int BTN_TRIGGER = 0x120; + public static final int BTN_THUMB = 0x121; + public static final int BTN_THUMB2 = 0x122; + public static final int BTN_TOP = 0x123; + public static final int BTN_TOP2 = 0x124; + public static final int BTN_PINKIE = 0x125; + public static final int BTN_BASE = 0x126; + public static final int BTN_BASE2 = 0x127; + public static final int BTN_BASE3 = 0x128; + public static final int BTN_BASE4 = 0x129; + public static final int BTN_BASE5 = 0x12a; + public static final int BTN_BASE6 = 0x12b; + public static final int BTN_DEAD = 0x12f; + + public static final int BTN_GAMEPAD = 0x130; + public static final int BTN_A = 0x130; + public static final int BTN_B = 0x131; + public static final int BTN_C = 0x132; + public static final int BTN_X = 0x133; + public static final int BTN_Y = 0x134; + public static final int BTN_Z = 0x135; + public static final int BTN_TL = 0x136; + public static final int BTN_TR = 0x137; + public static final int BTN_TL2 = 0x138; + public static final int BTN_TR2 = 0x139; + public static final int BTN_SELECT = 0x13a; + public static final int BTN_START = 0x13b; + public static final int BTN_MODE = 0x13c; + public static final int BTN_THUMBL = 0x13d; + public static final int BTN_THUMBR = 0x13e; + + public static final int BTN_DIGI = 0x140; + public static final int BTN_TOOL_PEN = 0x140; + public static final int BTN_TOOL_RUBBER = 0x141; + public static final int BTN_TOOL_BRUSH = 0x142; + public static final int BTN_TOOL_PENCIL = 0x143; + public static final int BTN_TOOL_AIRBRUSH = 0x144; + public static final int BTN_TOOL_FINGER = 0x145; + public static final int BTN_TOOL_MOUSE = 0x146; + public static final int BTN_TOOL_LENS = 0x147; + public static final int BTN_TOUCH = 0x14a; + public static final int BTN_STYLUS = 0x14b; + public static final int BTN_STYLUS2 = 0x14c; + public static final int BTN_TOOL_DOUBLETAP = 0x14d; + public static final int BTN_TOOL_TRIPLETAP = 0x14e; + + public static final int BTN_WHEEL = 0x150; + public static final int BTN_GEAR_DOWN = 0x150; + public static final int BTN_GEAR_UP = 0x151; + + public static final int BTN_LAST = 0x15f; + + // Relative axes (EV_REL) scan codes. + + public static final int REL_X = 0x00; + public static final int REL_Y = 0x01; + public static final int REL_Z = 0x02; + public static final int REL_RX = 0x03; + public static final int REL_RY = 0x04; + public static final int REL_RZ = 0x05; + public static final int REL_HWHEEL = 0x06; + public static final int REL_DIAL = 0x07; + public static final int REL_WHEEL = 0x08; + public static final int REL_MISC = 0x09; + public static final int REL_MAX = 0x0f; + + // Absolute axes (EV_ABS) scan codes. + + public static final int ABS_X = 0x00; + public static final int ABS_Y = 0x01; + public static final int ABS_Z = 0x02; + public static final int ABS_RX = 0x03; + public static final int ABS_RY = 0x04; + public static final int ABS_RZ = 0x05; + public static final int ABS_THROTTLE = 0x06; + public static final int ABS_RUDDER = 0x07; + public static final int ABS_WHEEL = 0x08; + public static final int ABS_GAS = 0x09; + public static final int ABS_BRAKE = 0x0a; + public static final int ABS_HAT0X = 0x10; + public static final int ABS_HAT0Y = 0x11; + public static final int ABS_HAT1X = 0x12; + public static final int ABS_HAT1Y = 0x13; + public static final int ABS_HAT2X = 0x14; + public static final int ABS_HAT2Y = 0x15; + public static final int ABS_HAT3X = 0x16; + public static final int ABS_HAT3Y = 0x17; + public static final int ABS_PRESSURE = 0x18; + public static final int ABS_DISTANCE = 0x19; + public static final int ABS_TILT_X = 0x1a; + public static final int ABS_TILT_Y = 0x1b; + public static final int ABS_TOOL_WIDTH = 0x1c; + public static final int ABS_VOLUME = 0x20; + public static final int ABS_MISC = 0x28; + public static final int ABS_MAX = 0x3f; + + public int deviceId; + public int type; + public int scancode; + public int keycode; + public int flags; + public int value; + public long when; +} diff --git a/core/java/android/view/SoundEffectConstants.java b/core/java/android/view/SoundEffectConstants.java new file mode 100644 index 0000000..4a77af4 --- /dev/null +++ b/core/java/android/view/SoundEffectConstants.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008 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.view; + +/** + * Constants to be used to play sound effects via {@link View#playSoundEffect(int)} + */ +public class SoundEffectConstants { + + private SoundEffectConstants() {} + + public static final int CLICK = 0; + + public static final int NAVIGATION_LEFT = 1; + public static final int NAVIGATION_UP = 2; + public static final int NAVIGATION_RIGHT = 3; + public static final int NAVIGATION_DOWN = 4; + + /** + * Get the sonification constant for the focus directions. + * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, + * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD} + * or {@link View#FOCUS_BACKWARD} + + * @return The appropriate sonification constant. + */ + public static int getContantForFocusDirection(int direction) { + switch (direction) { + case View.FOCUS_RIGHT: + return SoundEffectConstants.NAVIGATION_RIGHT; + case View.FOCUS_FORWARD: + case View.FOCUS_DOWN: + return SoundEffectConstants.NAVIGATION_DOWN; + case View.FOCUS_LEFT: + return SoundEffectConstants.NAVIGATION_LEFT; + case View.FOCUS_BACKWARD: + case View.FOCUS_UP: + return SoundEffectConstants.NAVIGATION_UP; + } + throw new IllegalArgumentException("direction must be one of " + + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}."); + } +} diff --git a/core/java/android/view/SubMenu.java b/core/java/android/view/SubMenu.java new file mode 100644 index 0000000..e981486 --- /dev/null +++ b/core/java/android/view/SubMenu.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 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.view; + +import android.graphics.drawable.Drawable; + +/** + * Subclass of {@link Menu} for sub menus. + * <p> + * Sub menus do not support item icons, or nested sub menus. + */ + +public interface SubMenu extends Menu { + /** + * Sets the submenu header's title to the title given in <var>titleRes</var> + * resource identifier. + * + * @param titleRes The string resource identifier used for the title. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderTitle(int titleRes); + + /** + * Sets the submenu header's title to the title given in <var>title</var>. + * + * @param title The character sequence used for the title. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderTitle(CharSequence title); + + /** + * Sets the submenu header's icon to the icon given in <var>iconRes</var> + * resource id. + * + * @param iconRes The resource identifier used for the icon. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderIcon(int iconRes); + + /** + * Sets the submenu header's icon to the icon given in <var>icon</var> + * {@link Drawable}. + * + * @param icon The {@link Drawable} used for the icon. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderIcon(Drawable icon); + + /** + * Sets the header of the submenu to the {@link View} given in + * <var>view</var>. This replaces the header title and icon (and those + * replace this). + * + * @param view The {@link View} used for the header. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setHeaderView(View view); + + /** + * Clears the header of the submenu. + */ + public void clearHeader(); + + /** + * Change the icon associated with this submenu's item in its parent menu. + * + * @see MenuItem#setIcon(int) + * @param iconRes The new icon (as a resource ID) to be displayed. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setIcon(int iconRes); + + /** + * Change the icon associated with this submenu's item in its parent menu. + * + * @see MenuItem#setIcon(Drawable) + * @param icon The new icon (as a Drawable) to be displayed. + * @return This SubMenu so additional setters can be called. + */ + public SubMenu setIcon(Drawable icon); + + /** + * Gets the {@link MenuItem} that represents this submenu in the parent + * menu. Use this for setting additional item attributes. + * + * @return The {@link MenuItem} that launches the submenu when invoked. + */ + public MenuItem getItem(); +} diff --git a/core/java/android/view/Surface.aidl b/core/java/android/view/Surface.aidl new file mode 100644 index 0000000..90bf37a --- /dev/null +++ b/core/java/android/view/Surface.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/view/Surface.aidl +** +** Copyright 2007, 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.view; + +parcelable Surface; diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java new file mode 100644 index 0000000..54ccf33 --- /dev/null +++ b/core/java/android/view/Surface.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2007 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.view; + +import android.graphics.*; +import android.os.Parcelable; +import android.os.Parcel; +import android.util.Log; + +/** + * Handle on to a raw buffer that is being managed by the screen compositor. + */ +public class Surface implements Parcelable { + private static final String LOG_TAG = "Surface"; + + /* flags used in constructor (keep in sync with ISurfaceComposer.h) */ + + /** Surface is created hidden */ + public static final int HIDDEN = 0x00000004; + + /** The surface is to be used by hardware accelerators or DMA engines */ + public static final int HARDWARE = 0x00000010; + + /** Implies "HARDWARE", the surface is to be used by the GPU + * additionally the backbuffer is never preserved for these + * surfaces. */ + public static final int GPU = 0x00000028; + + /** The surface contains secure content, special measures will + * be taken to disallow the surface's content to be copied from + * another process. In particular, screenshots and VNC servers will + * be disabled, but other measures can take place, for instance the + * surface might not be hardware accelerated. */ + public static final int SECURE = 0x00000080; + + /** Creates a surface where color components are interpreted as + * "non pre-multiplied" by their alpha channel. Of course this flag is + * meaningless for surfaces without an alpha channel. By default + * surfaces are pre-multiplied, which means that each color component is + * already multiplied by its alpha value. In this case the blending + * equation used is: + * + * DEST = SRC + DEST * (1-SRC_ALPHA) + * + * By contrast, non pre-multiplied surfaces use the following equation: + * + * DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA) + * + * pre-multiplied surfaces must always be used if transparent pixels are + * composited on top of each-other into the surface. A pre-multiplied + * surface can never lower the value of the alpha component of a given + * pixel. + * + * In some rare situations, a non pre-multiplied surface is preferable. + * + */ + public static final int NON_PREMULTIPLIED = 0x00000100; + + /** + * Creates a surface without a rendering buffer. Instead, the content + * of the surface must be pushed by an external entity. This is type + * of surface can be used for efficient camera preview or movie + * play back. + */ + public static final int PUSH_BUFFERS = 0x00000200; + + /** Creates a normal surface. This is the default */ + public static final int FX_SURFACE_NORMAL = 0x00000000; + + /** Creates a Blur surface. Everything behind this surface is blurred + * by some amount. The quality and refresh speed of the blur effect + * is not settable or guaranteed. + * It is an error to lock a Blur surface, since it doesn't have + * a backing store. + */ + public static final int FX_SURFACE_BLUR = 0x00010000; + + /** Creates a Dim surface. Everything behind this surface is dimmed + * by the amount specified in setAlpha(). + * It is an error to lock a Dim surface, since it doesn't have + * a backing store. + */ + public static final int FX_SURFACE_DIM = 0x00020000; + + /** Mask used for FX values above */ + public static final int FX_SURFACE_MASK = 0x000F0000; + + /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */ + + /** Hide the surface. Equivalent to calling hide() */ + public static final int SURFACE_HIDDEN = 0x01; + + /** Freeze the surface. Equivalent to calling freeze() */ + public static final int SURACE_FROZEN = 0x02; + + /** Enable dithering when compositing this surface */ + public static final int SURFACE_DITHER = 0x04; + + public static final int SURFACE_BLUR_FREEZE= 0x10; + + /* orientations for setOrientation() */ + public static final int ROTATION_0 = 0; + public static final int ROTATION_90 = 1; + public static final int ROTATION_180 = 2; + public static final int ROTATION_270 = 3; + + @SuppressWarnings("unused") + private int mSurface; + @SuppressWarnings("unused") + private int mSaveCount; + @SuppressWarnings("unused") + private Canvas mCanvas; + + /** + * Exception thrown when a surface couldn't be created or resized + */ + public static class OutOfResourcesException extends Exception { + public OutOfResourcesException() { + } + public OutOfResourcesException(String name) { + super(name); + } + } + + /* + * We use a class initializer to allow the native code to cache some + * field offsets. + */ + native private static void nativeClassInit(); + static { nativeClassInit(); } + + + /** + * create a surface + * {@hide} + */ + public Surface(SurfaceSession s, + int pid, int display, int w, int h, int format, int flags) + throws OutOfResourcesException { + mCanvas = new Canvas(); + init(s,pid,display,w,h,format,flags); + } + + /** + * Create an empty surface, which will later be filled in by + * readFromParcel(). + * {@hide} + */ + public Surface() { + mCanvas = new Canvas(); + } + + /** + * Copy another surface to this one. This surface now holds a reference + * to the same data as the original surface, and is -not- the owner. + * {@hide} + */ + public native void copyFrom(Surface o); + + /** + * Does this object hold a valid surface? Returns true if it holds + * a physical surface, so lockCanvas() will succeed. Otherwise + * returns false. + */ + public native boolean isValid(); + + /** Call this free the surface up. {@hide} */ + public native void clear(); + + /** draw into a surface */ + public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException { + /* the dirty rectangle may be expanded to the surface's size, if + * for instance it has been resized or if the bits were lost, since + * the last call. + */ + return lockCanvasNative(dirty); + } + + private native Canvas lockCanvasNative(Rect dirty); + + /** unlock the surface and asks a page flip */ + public native void unlockCanvasAndPost(Canvas canvas); + + /** + * unlock the surface. the screen won't be updated until + * post() or postAll() is called + */ + public native void unlockCanvas(Canvas canvas); + + /** start/end a transaction {@hide} */ + public static native void openTransaction(); + /** {@hide} */ + public static native void closeTransaction(); + + /** + * Freezes the specified display, No updating of the screen will occur + * until unfreezeDisplay() is called. Everything else works as usual though, + * in particular transactions. + * @param display + * {@hide} + */ + public static native void freezeDisplay(int display); + + /** + * resume updating the specified display. + * @param display + * {@hide} + */ + public static native void unfreezeDisplay(int display); + + /** + * set the orientation of the given display. + * @param display + * @param orientation + */ + public static native void setOrientation(int display, int orientation); + + /** + * set surface parameters. + * needs to be inside open/closeTransaction block + */ + public native void setLayer(int zorder); + public native void setPosition(int x, int y); + public native void setSize(int w, int h); + + public native void hide(); + public native void show(); + public native void setTransparentRegionHint(Region region); + public native void setAlpha(float alpha); + public native void setMatrix(float dsdx, float dtdx, + float dsdy, float dtdy); + + public native void freeze(); + public native void unfreeze(); + + public native void setFreezeTint(int tint); + + public native void setFlags(int flags, int mask); + + @Override + public String toString() { + return "Surface(native-token=" + mSurface + ")"; + } + + private Surface(Parcel source) throws OutOfResourcesException { + init(source); + } + + public int describeContents() { + return 0; + } + + public native void readFromParcel(Parcel source); + public native void writeToParcel(Parcel dest, int flags); + + public static final Parcelable.Creator<Surface> CREATOR + = new Parcelable.Creator<Surface>() + { + public Surface createFromParcel(Parcel source) { + try { + return new Surface(source); + } catch (Exception e) { + Log.e(LOG_TAG, "Exception creating surface from parcel", e); + } + return null; + } + + public Surface[] newArray(int size) { + return new Surface[size]; + } + }; + + /* no user serviceable parts here ... */ + @Override + protected void finalize() throws Throwable { + clear(); + } + + private native void init(SurfaceSession s, + int pid, int display, int w, int h, int format, int flags) + throws OutOfResourcesException; + + private native void init(Parcel source); +} diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java new file mode 100644 index 0000000..21a72e7 --- /dev/null +++ b/core/java/android/view/SurfaceHolder.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2006 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.view; + +import android.graphics.Canvas; +import android.graphics.Rect; +import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_NORMAL; +import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_HARDWARE; +import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_GPU; +import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_PUSH_BUFFERS; + +/** + * Abstract interface to someone holding a display surface. Allows you to + * control the surface size and format, edit the pixels in the surface, and + * monitor changes to the surface. This interface is typically available + * through the {@link SurfaceView} class. + * + * <p>When using this interface from a thread different than the one running + * its {@link SurfaceView}, you will want to carefully read the + * {@link #lockCanvas} and {@link Callback#surfaceCreated Callback.surfaceCreated}. + */ +public interface SurfaceHolder { + /** + * Surface type. + * + * @see #SURFACE_TYPE_NORMAL + * @see #SURFACE_TYPE_HARDWARE + * @see #SURFACE_TYPE_GPU + * @see #SURFACE_TYPE_PUSH_BUFFERS + */ + + /** Surface type: creates a regular surface, usually in main, non + * contiguous, cached/buffered RAM. */ + public static final int SURFACE_TYPE_NORMAL = MEMORY_TYPE_NORMAL; + /** Surface type: creates a suited to be used with DMA engines and + * hardware accelerators. */ + public static final int SURFACE_TYPE_HARDWARE = MEMORY_TYPE_HARDWARE; + /** Surface type: creates a surface suited to be used with the GPU */ + public static final int SURFACE_TYPE_GPU = MEMORY_TYPE_GPU; + /** Surface type: creates a "push" surface, that is a surface that + * doesn't owns its buffers. With such a surface lockCanvas will fail. */ + public static final int SURFACE_TYPE_PUSH_BUFFERS = MEMORY_TYPE_PUSH_BUFFERS; + + /** + * Exception that is thrown from {@link #lockCanvas} when called on a Surface + * whose is SURFACE_TYPE_PUSH_BUFFERS. + */ + public static class BadSurfaceTypeException extends RuntimeException { + public BadSurfaceTypeException() { + } + + public BadSurfaceTypeException(String name) { + super(name); + } + } + + /** + * A client may implement this interface to receive information about + * changes to the surface. When used with a {@link SurfaceView}, the + * Surface being held is only available between calls to + * {@link #surfaceCreated(SurfaceHolder)} and + * {@link #surfaceDestroyed(SurfaceHolder). The Callback is set with + * {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method. + */ + public interface Callback { + /** + * This is called immediately after the surface is first created. + * Implementations of this should start up whatever rendering code + * they desire. Note that only one thread can ever draw into + * a {@link Surface}, so you should not draw into the Surface here + * if your normal rendering will be in another thread. + * + * @param holder The SurfaceHolder whose surface is being created. + */ + public void surfaceCreated(SurfaceHolder holder); + + /** + * This is called immediately after any structural changes (format or + * size) have been made to the surface. You should at this point update + * the imagery in the surface. This method is always called at least + * once, after {@link #surfaceCreated}. + * + * @param holder The SurfaceHolder whose surface has changed. + * @param format The new PixelFormat of the surface. + * @param width The new width of the surface. + * @param height The new height of the surface. + */ + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height); + + /** + * This is called immediately before a surface is being destroyed. After + * returning from this call, you should no longer try to access this + * surface. If you have a rendering thread that directly accesses + * the surface, you must ensure that thread is no longer touching the + * Surface before returning from this function. + * + * @param holder The SurfaceHolder whose surface is being destroyed. + */ + public void surfaceDestroyed(SurfaceHolder holder); + } + + /** + * Add a Callback interface for this holder. There can several Callback + * interfaces associated to a holder. + * + * @param callback The new Callback interface. + */ + public void addCallback(Callback callback); + + /** + * Removes a previously added Callback interface from this holder. + * + * @param callback The Callback interface to remove. + */ + public void removeCallback(Callback callback); + + /** + * Use this method to find out if the surface is in the process of being + * created from Callback methods. This is intended to be used with + * {@link Callback#surfaceChanged}. + * + * @return true if the surface is in the process of being created. + */ + public boolean isCreating(); + + /** + * Sets the surface's type. Surfaces intended to be used with OpenGL ES + * should be of SURFACE_TYPE_GPU, surfaces accessed by DMA engines and + * hardware accelerators should be of type SURFACE_TYPE_HARDWARE. + * Failing to set the surface's type appropriately could result in + * degraded performance or failure. + * + * @param type The surface's memory type. + */ + public void setType(int type); + + /** + * Make the surface a fixed size. It will never change from this size. + * When working with a {link SurfaceView}, this must be called from the + * same thread running the SurfaceView's window. + * + * @param width The surface's width. + * @param height The surface's height. + */ + public void setFixedSize(int width, int height); + + /** + * Allow the surface to resized based on layout of its container (this is + * the default). When this is enabled, you should monitor + * {@link Callback#surfaceChanged} for changes to the size of the surface. + * When working with a {link SurfaceView}, this must be called from the + * same thread running the SurfaceView's window. + */ + public void setSizeFromLayout(); + + /** + * Set the desired PixelFormat of the surface. The default is OPAQUE. + * When working with a {link SurfaceView}, this must be called from the + * same thread running the SurfaceView's window. + * + * @param format A constant from PixelFormat. + * + * @see android.graphics.PixelFormat + */ + public void setFormat(int format); + + /** + * Enable or disable option to keep the screen turned on while this + * surface is displayed. The default is false, allowing it to turn off. + * Enabling the option effectivelty. + * This is safe to call from any thread. + * + * @param screenOn Supply to true to force the screen to stay on, false + * to allow it to turn off. + */ + public void setKeepScreenOn(boolean screenOn); + + /** + * Start editing the pixels in the surface. The returned Canvas can be used + * to draw into the surface's bitmap. A null is returned if the surface has + * not been created or otherwise can not be edited. You will usually need + * to implement {@link Callback#surfaceCreated Callback.surfaceCreated} + * to find out when the Surface is available for use. + * + * <p>The content of the Surface is never preserved between unlockCanvas() and + * lockCanvas(), for this reason, every pixel within the Surface area + * must be written. The only exception to this rule is when a dirty + * rectangle is specified, in which case, non dirty pixels will be + * preserved. + * + * <p>If you call this repeatedly when the Surface is not ready (before + * {@link Callback#surfaceCreated Callback.surfaceCreated} or after + * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed}), your calls + * will be throttled to a slow rate in order to avoid consuming CPU. + * + * <p>If null is not returned, this function internally holds a lock until + * the corresponding {@link #unlockCanvasAndPost} call, preventing + * {@link SurfaceView} from creating, destroying, or modifying the surface + * while it is being drawn. This can be more convenience than accessing + * the Surface directly, as you do not need to do special synchronization + * with a drawing thread in {@link Callback#surfaceDestroyed + * Callback.surfaceDestroyed}. + * + * @return Canvas Use to draw into the surface. + */ + public Canvas lockCanvas(); + + + /** + * Just like {@link #lockCanvas()} but allows to specify a dirty rectangle. + * Every + * pixel within that rectangle must be written; however pixels outside + * the dirty rectangle will be preserved by the next call to lockCanvas(). + * + * @see android.view.SurfaceHolder#lockCanvas + * + * @param dirty Area of the Surface that will be modified. + * @return Canvas Use to draw into the surface. + */ + public Canvas lockCanvas(Rect dirty); + + /** + * Finish editing pixels in the surface. After this call, the surface's + * current pixels will be shown on the screen, but its content is lost, + * in particular there is no guarantee that the content of the Surface + * will remain unchanged when lockCanvas() is called again. + * + * @see android.view.SurfaceHolder.lockCanvas + * + * @param canvas The Canvas previously returned by lockCanvas(). + */ + public void unlockCanvasAndPost(Canvas canvas); + + /** + * Retrieve the current size of the surface. Note: do not modify the + * returned Rect. This is only safe to call from the thread of + * {@link SurfaceView}'s window, or while inside of + * {@link #lockCanvas()}. + * + * @return Rect The surface's dimensions. The left and top are always 0. + */ + public Rect getSurfaceFrame(); + + /** + * Direct access to the surface object. The Surface may not always be + * available -- for example when using a {@link SurfaceView} the holder's + * Surface is not created until the view has been attached to the window + * manager and performed a layout in order to determine the dimensions + * and screen position of the Surface. You will thus usually need + * to implement {@link Callback#surfaceCreated Callback.surfaceCreated} + * to find out when the Surface is available for use. + * + * <p>Note that if you directly access the Surface from another thread, + * it is critical that you correctly implement + * {@link Callback#surfaceCreated Callback.surfaceCreated} and + * {@link Callback#surfaceDestroyed Callback.surfaceDestroyed} to ensure + * that thread only accesses the Surface while it is valid, and that the + * Surface does not get destroyed while the thread is using it. + * + * <p>This method is intended to be used by frameworks which often need + * direct access to the Surface object (usually to pass it to native code). + * When designing APIs always use SurfaceHolder to pass surfaces around + * as opposed to the Surface object itself. A rule of thumb is that + * application code should never have to call this method. + * + * @return Surface The surface. + */ + public Surface getSurface(); +} diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java new file mode 100644 index 0000000..2a04675 --- /dev/null +++ b/core/java/android/view/SurfaceSession.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2006 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.view; + + +/** + * An instance of this class represents a connection to the surface + * flinger, in which you can create one or more Surface instances that will + * be composited to the screen. + * {@hide} + */ +public class SurfaceSession { + /** Create a new connection with the surface flinger. */ + public SurfaceSession() { + init(); + } + + /** Forcibly detach native resources associated with this object. + * Unlike destroy(), after this call any surfaces that were created + * from the session will no longer work. The session itself is destroyed. + */ + public native void kill(); + + /* no user serviceable parts here ... */ + @Override + protected void finalize() throws Throwable { + destroy(); + } + + private native void init(); + private native void destroy(); + + private int mClient; +} + diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java new file mode 100644 index 0000000..57689f2 --- /dev/null +++ b/core/java/android/view/SurfaceView.java @@ -0,0 +1,593 @@ +/* + * Copyright (C) 2006 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.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.ParcelFileDescriptor; +import android.util.AttributeSet; +import android.util.Config; +import android.util.Log; +import java.util.ArrayList; + +import java.util.concurrent.locks.ReentrantLock; + +/** + * Provides a dedicated drawing surface embedded inside of a view hierarchy. + * You can control the format of this surface and, if you like, its size; the + * SurfaceView takes care of placing the surface at the correct location on the + * screen + * + * <p>The surface is Z ordered so that it is behind the window holding its + * SurfaceView; the SurfaceView punches a hole in its window to allow its + * surface to be displayed. The view hierarchy will take care of correctly + * compositing with the Surface any siblings of the SurfaceView that would + * normally appear on top of it. This can be used to place overlays such as + * buttons on top of the Surface, though note however that it can have an + * impact on performance since a full alpha-blended composite will be performed + * each time the Surface changes. + * + * <p>Access to the underlying surface is provided via the SurfaceHolder interface, + * which can be retrieved by calling {@link #getHolder}. + * + * <p>The Surface will be created for you while the SurfaceView's window is + * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated} + * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the + * Surface is created and destroyed as the window is shown and hidden. + * + * <p>One of the purposes of this class is to provide a surface in which a + * secondary thread can render in to the screen. If you are going to use it + * this way, you need to be aware of some threading semantics: + * + * <ul> + * <li> All SurfaceView and + * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called + * from the thread running the SurfaceView's window (typically the main thread + * of the application). They thus need to correctly synchronize with any + * state that is also touched by the drawing thread. + * <li> You must ensure that the drawing thread only touches the underlying + * Surface while it is valid -- between + * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()} + * and + * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}. + * </ul> + */ +public class SurfaceView extends View { + static private final String TAG = "SurfaceView"; + static private final boolean DEBUG = false; + static private final boolean localLOGV = DEBUG ? true : Config.LOGV; + + final ArrayList<SurfaceHolder.Callback> mCallbacks + = new ArrayList<SurfaceHolder.Callback>(); + + final ReentrantLock mSurfaceLock = new ReentrantLock(); + final Surface mSurface = new Surface(); + boolean mDrawingStopped = true; + + final WindowManager.LayoutParams mLayout + = new WindowManager.LayoutParams(); + IWindowSession mSession; + MyWindow mWindow; + final Rect mWinFrame = new Rect(); + final Rect mCoveredInsets = new Rect(); + + static final int KEEP_SCREEN_ON_MSG = 1; + static final int GET_NEW_SURFACE_MSG = 2; + + boolean mIsCreating = false; + + final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case KEEP_SCREEN_ON_MSG: { + setKeepScreenOn(msg.arg1 != 0); + } break; + case GET_NEW_SURFACE_MSG: { + handleGetNewSurface(); + } break; + } + } + }; + + boolean mRequestedVisible = false; + int mRequestedWidth = -1; + int mRequestedHeight = -1; + int mRequestedFormat = PixelFormat.OPAQUE; + int mRequestedType = -1; + + boolean mHaveFrame = false; + boolean mDestroyReportNeeded = false; + boolean mNewSurfaceNeeded = false; + long mLastLockTime = 0; + + boolean mVisible = false; + int mLeft = -1; + int mTop = -1; + int mWidth = -1; + int mHeight = -1; + int mFormat = -1; + int mType = -1; + final Rect mSurfaceFrame = new Rect(); + + public SurfaceView(Context context) { + super(context); + setWillNotDraw(true); + } + + public SurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(true); + } + + public SurfaceView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setWillNotDraw(true); + } + + /** + * Return the SurfaceHolder providing access and control over this + * SurfaceView's underlying surface. + * + * @return SurfaceHolder The holder of the surface. + */ + public SurfaceHolder getHolder() { + return mSurfaceHolder; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mParent.requestTransparentRegion(this); + mSession = getWindowSession(); + mLayout.token = getWindowToken(); + mLayout.setTitle("SurfaceView"); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mRequestedVisible = visibility == VISIBLE; + updateWindow(false); + } + + @Override + protected void onDetachedFromWindow() { + mRequestedVisible = false; + updateWindow(false); + mHaveFrame = false; + if (mWindow != null) { + try { + mSession.remove(mWindow); + } catch (RemoteException ex) { + } + mWindow = null; + } + mSession = null; + mLayout.token = null; + + super.onDetachedFromWindow(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = getDefaultSize(mRequestedWidth, widthMeasureSpec); + int height = getDefaultSize(mRequestedHeight, heightMeasureSpec); + setMeasuredDimension(width, height); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + updateWindow(false); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateWindow(false); + } + + @Override + public boolean gatherTransparentRegion(Region region) { + boolean opaque = true; + if ((mPrivateFlags & SKIP_DRAW) == 0) { + // this view draws, remove it from the transparent region + opaque = super.gatherTransparentRegion(region); + } else if (region != null) { + int w = getWidth(); + int h = getHeight(); + if (w>0 && h>0) { + getLocationInWindow(mLocation); + // otherwise, punch a hole in the whole hierarchy + int l = mLocation[0]; + int t = mLocation[1]; + region.op(l, t, l+w, t+h, Region.Op.UNION); + } + } + if (PixelFormat.formatHasAlpha(mRequestedFormat)) { + opaque = false; + } + return opaque; + } + + @Override + public void draw(Canvas canvas) { + // draw() is not called when SKIP_DRAW is set + if ((mPrivateFlags & SKIP_DRAW) == 0) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + super.draw(canvas); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + // if SKIP_DRAW is cleared, draw() has already punched a hole + if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + // punch a whole in the view-hierarchy below us + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + // reposition ourselves where the surface is + mHaveFrame = true; + updateWindow(false); + super.dispatchDraw(canvas); + } + + private void updateWindow(boolean force) { + if (!mHaveFrame) { + return; + } + + int myWidth = mRequestedWidth; + if (myWidth <= 0) myWidth = getWidth(); + int myHeight = mRequestedHeight; + if (myHeight <= 0) myHeight = getHeight(); + + getLocationInWindow(mLocation); + final boolean creating = mWindow == null; + final boolean formatChanged = mFormat != mRequestedFormat; + final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight; + final boolean visibleChanged = mVisible != mRequestedVisible + || mNewSurfaceNeeded; + final boolean typeChanged = mType != mRequestedType; + if (force || creating || formatChanged || sizeChanged || visibleChanged + || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) { + + if (localLOGV) Log.i(TAG, "Changes: creating=" + creating + + " format=" + formatChanged + " size=" + sizeChanged + + " visible=" + visibleChanged + + " left=" + (mLeft != mLocation[0]) + + " top=" + (mTop != mLocation[1])); + + try { + final boolean visible = mVisible = mRequestedVisible; + mLeft = mLocation[0]; + mTop = mLocation[1]; + mWidth = myWidth; + mHeight = myHeight; + mFormat = mRequestedFormat; + mType = mRequestedType; + + mLayout.x = mLeft; + mLayout.y = mTop; + mLayout.width = getWidth(); + mLayout.height = getHeight(); + mLayout.format = mRequestedFormat; + mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + | WindowManager.LayoutParams.FLAG_SCALED + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + ; + + mLayout.memoryType = mRequestedType; + + if (mWindow == null) { + mWindow = new MyWindow(); + mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; + mLayout.gravity = Gravity.LEFT|Gravity.TOP; + mSession.add(mWindow, mLayout, + mVisible ? VISIBLE : GONE, mCoveredInsets); + } + + if (visibleChanged && (!visible || mNewSurfaceNeeded)) { + reportSurfaceDestroyed(); + } + + mNewSurfaceNeeded = false; + + mSurfaceLock.lock(); + mDrawingStopped = !visible; + final int relayoutResult = mSession.relayout( + mWindow, mLayout, mWidth, mHeight, + visible ? VISIBLE : GONE, mWinFrame, mCoveredInsets, mSurface); + if (localLOGV) Log.i(TAG, "New surface: " + mSurface + + ", vis=" + visible + ", frame=" + mWinFrame); + mSurfaceFrame.left = 0; + mSurfaceFrame.top = 0; + mSurfaceFrame.right = mWinFrame.width(); + mSurfaceFrame.bottom = mWinFrame.height(); + mSurfaceLock.unlock(); + + try { + if (visible) { + mDestroyReportNeeded = true; + + SurfaceHolder.Callback callbacks[]; + synchronized (mCallbacks) { + callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; + mCallbacks.toArray(callbacks); + } + + if (visibleChanged) { + mIsCreating = true; + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceCreated(mSurfaceHolder); + } + } + if (creating || formatChanged || sizeChanged + || visibleChanged) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight); + } + } + } + } finally { + mIsCreating = false; + if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { + mSession.finishDrawing(mWindow); + } + } + } catch (RemoteException ex) { + } + if (localLOGV) Log.v( + TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y + + " w=" + mLayout.width + " h=" + mLayout.height + + ", frame=" + mSurfaceFrame); + } + } + + private void reportSurfaceDestroyed() { + if (mDestroyReportNeeded) { + mDestroyReportNeeded = false; + SurfaceHolder.Callback callbacks[]; + synchronized (mCallbacks) { + callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; + mCallbacks.toArray(callbacks); + } + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } + } + super.onDetachedFromWindow(); + } + + void handleGetNewSurface() { + mNewSurfaceNeeded = true; + updateWindow(false); + } + + private class MyWindow extends IWindow.Stub { + public void resized(int w, int h, boolean reportDraw) { + if (localLOGV) Log.v( + "SurfaceView", SurfaceView.this + " got resized: w=" + + w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight); + synchronized (this) { + if (mCurWidth != w || mCurHeight != h) { + mCurWidth = w; + mCurHeight = h; + } + if (reportDraw) { + try { + mSession.finishDrawing(mWindow); + } catch (RemoteException e) { + } + } + } + } + + public void dispatchKey(KeyEvent event) { + //Log.w("SurfaceView", "Unexpected key event in surface: " + event); + if (mSession != null && mSurface != null) { + try { + mSession.finishKey(mWindow); + } catch (RemoteException ex) { + } + } + } + + public void dispatchPointer(MotionEvent event, long eventTime) { + Log.w("SurfaceView", "Unexpected pointer event in surface: " + event); + //if (mSession != null && mSurface != null) { + // try { + // //mSession.finishKey(mWindow); + // } catch (RemoteException ex) { + // } + //} + } + + public void dispatchTrackball(MotionEvent event, long eventTime) { + Log.w("SurfaceView", "Unexpected trackball event in surface: " + event); + //if (mSession != null && mSurface != null) { + // try { + // //mSession.finishKey(mWindow); + // } catch (RemoteException ex) { + // } + //} + } + + public void dispatchAppVisibility(boolean visible) { + // The point of SurfaceView is to let the app control the surface. + } + + public void dispatchGetNewSurface() { + Message msg = mHandler.obtainMessage(GET_NEW_SURFACE_MSG); + mHandler.sendMessage(msg); + } + + public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { + Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled); + } + + public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { + } + + int mCurWidth = -1; + int mCurHeight = -1; + } + + private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { + + private static final String LOG_TAG = "SurfaceHolder"; + + public boolean isCreating() { + return mIsCreating; + } + + public void addCallback(Callback callback) { + synchronized (mCallbacks) { + // This is a linear search, but in practice we'll + // have only a couple callbacks, so it doesn't matter. + if (mCallbacks.contains(callback) == false) { + mCallbacks.add(callback); + } + } + } + + public void removeCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + public void setFixedSize(int width, int height) { + if (mRequestedWidth != width || mRequestedHeight != height) { + mRequestedWidth = width; + mRequestedHeight = height; + requestLayout(); + } + } + + public void setSizeFromLayout() { + if (mRequestedWidth != -1 || mRequestedHeight != -1) { + mRequestedWidth = mRequestedHeight = -1; + requestLayout(); + } + } + + public void setFormat(int format) { + mRequestedFormat = format; + if (mWindow != null) { + updateWindow(false); + } + } + + public void setType(int type) { + switch (type) { + case SURFACE_TYPE_NORMAL: + case SURFACE_TYPE_HARDWARE: + case SURFACE_TYPE_GPU: + case SURFACE_TYPE_PUSH_BUFFERS: + mRequestedType = type; + if (mWindow != null) { + updateWindow(false); + } + break; + } + } + + public void setKeepScreenOn(boolean screenOn) { + Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG); + msg.arg1 = screenOn ? 1 : 0; + mHandler.sendMessage(msg); + } + + public Canvas lockCanvas() { + return internalLockCanvas(null); + } + + public Canvas lockCanvas(Rect dirty) { + return internalLockCanvas(dirty); + } + + private final Canvas internalLockCanvas(Rect dirty) { + if (mType == SURFACE_TYPE_PUSH_BUFFERS) { + throw new BadSurfaceTypeException( + "Surface type is SURFACE_TYPE_PUSH_BUFFERS"); + } + mSurfaceLock.lock(); + + if (localLOGV) Log.i(TAG, "Locking canvas... stopped=" + + mDrawingStopped + ", win=" + mWindow); + + Canvas c = null; + if (!mDrawingStopped && mWindow != null) { + Rect frame = dirty != null ? dirty : mSurfaceFrame; + try { + c = mSurface.lockCanvas(frame); + } catch (Exception e) { + Log.e(LOG_TAG, "Exception locking surface", e); + } + } + + if (localLOGV) Log.i(TAG, "Returned canvas: " + c); + if (c != null) { + mLastLockTime = SystemClock.uptimeMillis(); + return c; + } + + // If the Surface is not ready to be drawn, then return null, + // but throttle calls to this function so it isn't called more + // than every 100ms. + long now = SystemClock.uptimeMillis(); + long nextTime = mLastLockTime + 100; + if (nextTime > now) { + try { + Thread.sleep(nextTime-now); + } catch (InterruptedException e) { + } + now = SystemClock.uptimeMillis(); + } + mLastLockTime = now; + mSurfaceLock.unlock(); + + return null; + } + + public void unlockCanvasAndPost(Canvas canvas) { + mSurface.unlockCanvasAndPost(canvas); + mSurfaceLock.unlock(); + } + + public Surface getSurface() { + return mSurface; + } + + public Rect getSurfaceFrame() { + return mSurfaceFrame; + } + }; +} + diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java new file mode 100644 index 0000000..057df92 --- /dev/null +++ b/core/java/android/view/TouchDelegate.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2008 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.view; + +import android.graphics.Rect; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +/** + * Helper class to handle situations where you want a view to have a larger touch area than its + * actual view bounds. The view whose touch area is changed is called the delegate view. This + * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an + * instance that specifies the bounds that should be mapped to the delegate and the delegate + * view itself. + * <p> + * The ancestor should then forward all of its touch events received in its + * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}. + * </p> + */ +public class TouchDelegate { + + /** + * View that should receive forwarded touch events + */ + private View mDelegateView; + + /** + * Bounds in local coordinates of the containing view that should be mapped to the delegate + * view. This rect is used for initial hit testing. + */ + private Rect mBounds; + + /** + * mBounds inflated to include some slop. This rect is to track whether the motion events + * should be considered to be be within the delegate view. + */ + private Rect mSlopBounds; + + /** + * True if the delegate had been targeted on a down event (intersected mBounds). + */ + private boolean mDelegateTargeted; + + /** + * The touchable region of the View extends above its actual extent. + */ + public static final int ABOVE = 1; + + /** + * The touchable region of the View extends below its actual extent. + */ + public static final int BELOW = 2; + + /** + * The touchable region of the View extends to the left of its + * actual extent. + */ + public static final int TO_LEFT = 4; + + /** + * The touchable region of the View extends to the right of its + * actual extent. + */ + public static final int TO_RIGHT = 8; + + /** + * Constructor + * + * @param bounds Bounds in local coordinates of the containing view that should be mapped to + * the delegate view + * @param delegateView The view that should receive motion events + */ + public TouchDelegate(Rect bounds, View delegateView) { + mBounds = bounds; + + int slop = ViewConfiguration.getTouchSlop(); + mSlopBounds = new Rect(bounds); + mSlopBounds.inset(-slop, -slop); + mDelegateView = delegateView; + } + + /** + * Will forward touch events to the delegate view if the event is within the bounds + * specified in the constructor. + * + * @param event The touch event to forward + * @return True if the event was forwarded to the delegate, false otherwise. + */ + public boolean onTouchEvent(MotionEvent event) { + int x = (int)event.getX(); + int y = (int)event.getY(); + boolean sendToDelegate = false; + boolean hit = true; + boolean handled = false; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + Rect bounds = mBounds; + + if (bounds.contains(x, y)) { + mDelegateTargeted = true; + sendToDelegate = true; + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_MOVE: + sendToDelegate = mDelegateTargeted; + if (sendToDelegate) { + Rect slopBounds = mSlopBounds; + if (!slopBounds.contains(x, y)) { + hit = false; + } + } + break; + case MotionEvent.ACTION_CANCEL: + sendToDelegate = mDelegateTargeted; + mDelegateTargeted = false; + break; + } + if (sendToDelegate) { + final View delegateView = mDelegateView; + + if (hit) { + // Offset event coordinates to be inside the target view + event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2); + } else { + // Offset event coordinates to be outside the target view (in case it does + // something like tracking pressed state) + int slop = ViewConfiguration.getTouchSlop(); + event.setLocation(-(slop * 2), -(slop * 2)); + } + handled = delegateView.dispatchTouchEvent(event); + } + return handled; + } +} diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java new file mode 100644 index 0000000..c80167e --- /dev/null +++ b/core/java/android/view/VelocityTracker.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2006 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.view; + +import android.util.Config; +import android.util.Log; + +/** + * Helper for tracking the velocity of touch events, for implementing + * flinging and other such gestures. Use {@link #obtain} to retrieve a + * new instance of the class when you are going to begin tracking, put + * the motion events you receive into it with {@link #addMovement(MotionEvent)}, + * and when you want to determine the velocity call + * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()} + * and {@link #getXVelocity()}. + */ +public final class VelocityTracker { + static final String TAG = "VelocityTracker"; + static final boolean DEBUG = false; + static final boolean localLOGV = DEBUG || Config.LOGV; + + static final int NUM_PAST = 10; + static final int LONGEST_PAST_TIME = 200; + + static final VelocityTracker[] mPool = new VelocityTracker[1]; + + final float mPastX[] = new float[NUM_PAST]; + final float mPastY[] = new float[NUM_PAST]; + final long mPastTime[] = new long[NUM_PAST]; + + float mYVelocity; + float mXVelocity; + + /** + * Retrieve a new VelocityTracker object to watch the velocity of a + * motion. Be sure to call {@link #recycle} when done. You should + * generally only maintain an active object while tracking a movement, + * so that the VelocityTracker can be re-used elsewhere. + * + * @return Returns a new VelocityTracker. + */ + static public VelocityTracker obtain() { + synchronized (mPool) { + VelocityTracker vt = mPool[0]; + if (vt != null) { + vt.clear(); + return vt; + } + return new VelocityTracker(); + } + } + + /** + * Return a VelocityTracker object back to be re-used by others. You must + * not touch the object after calling this function. + */ + public void recycle() { + synchronized (mPool) { + mPool[0] = this; + } + } + + private VelocityTracker() { + } + + /** + * Reset the velocity tracker back to its initial state. + */ + public void clear() { + mPastTime[0] = 0; + } + + /** + * Add a user's movement to the tracker. You should call this for the + * initial {@link MotionEvent#ACTION_DOWN}, the following + * {@link MotionEvent#ACTION_MOVE} events that you receive, and the + * final {@link MotionEvent#ACTION_UP}. You can, however, call this + * for whichever events you desire. + * + * @param ev The MotionEvent you received and would like to track. + */ + public void addMovement(MotionEvent ev) { + long time = ev.getEventTime(); + final int N = ev.getHistorySize(); + for (int i=0; i<N; i++) { + addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), + ev.getHistoricalEventTime(i)); + } + addPoint(ev.getX(), ev.getY(), time); + } + + private void addPoint(float x, float y, long time) { + int drop = -1; + int i; + if (localLOGV) Log.v(TAG, "Adding past y=" + y + " time=" + time); + final long[] pastTime = mPastTime; + for (i=0; i<NUM_PAST; i++) { + if (pastTime[i] == 0) { + break; + } else if (pastTime[i] < time-LONGEST_PAST_TIME) { + if (localLOGV) Log.v(TAG, "Dropping past too old at " + + i + " time=" + pastTime[i]); + drop = i; + } + } + if (localLOGV) Log.v(TAG, "Add index: " + i); + if (i == NUM_PAST && drop < 0) { + drop = 0; + } + if (drop == i) drop--; + final float[] pastX = mPastX; + final float[] pastY = mPastY; + if (drop >= 0) { + if (localLOGV) Log.v(TAG, "Dropping up to #" + drop); + final int start = drop+1; + final int count = NUM_PAST-drop-1; + System.arraycopy(pastX, start, pastX, 0, count); + System.arraycopy(pastY, start, pastY, 0, count); + System.arraycopy(pastTime, start, pastTime, 0, count); + i -= (drop+1); + } + pastX[i] = x; + pastY[i] = y; + pastTime[i] = time; + i++; + if (i < NUM_PAST) { + pastTime[i] = 0; + } + } + + /** + * Compute the current velocity based on the points that have been + * collected. Only call this when you actually want to retrieve velocity + * information, as it is relatively expensive. You can then retrieve + * the velocity with {@link #getXVelocity()} and + * {@link #getYVelocity()}. + * + * @param units The units you would like the velocity in. A value of 1 + * provides pixels per millisecond, 1000 provides pixels per second, etc. + */ + public void computeCurrentVelocity(int units) { + final float[] pastX = mPastX; + final float[] pastY = mPastY; + final long[] pastTime = mPastTime; + + // Kind-of stupid. + final float oldestX = pastX[0]; + final float oldestY = pastY[0]; + final long oldestTime = pastTime[0]; + float accumX = 0; + float accumY = 0; + int N=0; + while (N < NUM_PAST) { + if (pastTime[N] == 0) { + break; + } + N++; + } + // Skip the last received event, since it is probably pretty noisy. + if (N > 3) N--; + + for (int i=1; i < N; i++) { + final int dur = (int)(pastTime[i] - oldestTime); + if (dur == 0) continue; + float dist = pastX[i] - oldestX; + float vel = (dist/dur) * units; // pixels/frame. + if (accumX == 0) accumX = vel; + else accumX = (accumX + vel) * .5f; + + dist = pastY[i] - oldestY; + vel = (dist/dur) * units; // pixels/frame. + if (accumY == 0) accumY = vel; + else accumY = (accumY + vel) * .5f; + } + mXVelocity = accumX; + mYVelocity = accumY; + + if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" + + mXVelocity + " N=" + N); + } + + /** + * Retrieve the last computed X velocity. You must first call + * {@link #computeCurrentVelocity(int)} before calling this function. + * + * @return The previously computed X velocity. + */ + public float getXVelocity() { + return mXVelocity; + } + + /** + * Retrieve the last computed Y velocity. You must first call + * {@link #computeCurrentVelocity(int)} before calling this function. + * + * @return The previously computed Y velocity. + */ + public float getYVelocity() { + return mYVelocity; + } +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java new file mode 100644 index 0000000..30402f8 --- /dev/null +++ b/core/java/android/view/View.java @@ -0,0 +1,7481 @@ +/* + * Copyright (C) 2006 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.view; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.Shader; +import android.graphics.Point; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.EventLog; +import android.util.Log; +import android.util.SparseArray; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.animation.Animation; +import android.widget.ScrollBarDrawable; + +import com.android.internal.R; +import com.android.internal.view.menu.MenuBuilder; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * <p> + * The <code>View</code> class represents the basic UI building block. A view + * occupies a rectangular area on the screen and is responsible for drawing and + * event handling. <code>View</code> is the base class for <em>widgets</em>, + * used to create interactive graphical user interfaces. + * </p> + * + * <a name="Using"></a> + * <h3>Using Views</h3> + * <p> + * All of the views in a window are arranged in a single tree. You can add views + * either from code or by specifying a tree of views in one or more XML layout + * files. There are many specialized subclasses of views that act as controls or + * are capable of displaying text, images, or other content. + * </p> + * <p> + * Once you have created a tree of views, there are typically a few types of + * common operations you may wish to perform: + * <ul> + * <li><strong>Set properties:</strong> for example setting the text of a + * {@link android.widget.TextView}. The available properties and the methods + * that set them will vary among the different subclasses of views. Note that + * properties that are known at build time can be set in the XML layout + * files.</li> + * <li><strong>Set focus:</strong> The framework will handled moving focus in + * response to user input. To force focus to a specific view, call + * {@link #requestFocus}.</li> + * <li><strong>Set up listeners:</strong> Views allow clients to set listeners + * that will be notified when something interesting happens to the view. For + * example, all views will let you set a listener to be notified when the view + * gains or loses focus. You can register such a listener using + * {@link #setOnFocusChangeListener}. Other view subclasses offer more + * specialized listeners. For example, a Button exposes a listener to notify + * clients when the button is clicked.</li> + * <li><strong>Set visibility:</strong> You can hide or show views using + * {@link #setVisibility}.</li> + * </ul> + * </p> + * <p><em> + * Note: The Android framework is responsible for measuring, laying out and + * drawing views. You should not call methods that perform these actions on + * views yourself unless you are actually implementing a + * {@link android.view.ViewGroup}. + * </em></p> + * + * <a name="Lifecycle"></a> + * <h3>Implementing a Custom View</h3> + * + * <p> + * To implement a custom view, you will usually begin by providing overrides for + * some of the standard methods that the framework calls on all views. You do + * not need to override all of these methods. In fact, you can start by just + * overriding {@link #onDraw(android.graphics.Canvas)}. + * <table border="2" width="85%" align="center" cellpadding="5"> + * <thead> + * <tr><th>Category</th> <th>Methods</th> <th>Description</th></tr> + * </thead> + * + * <tbody> + * <tr> + * <td rowspan="2">Creation</td> + * <td>Constructors</td> + * <td>There is a form of the constructor that are called when the view + * is created from code and a form that is called when the view is + * inflated from a layout file. The second form should parse and apply + * any attributes defined in the layout file. + * </td> + * </tr> + * <tr> + * <td><code>{@link #onFinishInflate()}</code></td> + * <td>Called after a view and all of its children has been inflated + * from XML.</td> + * </tr> + * + * <tr> + * <td rowspan="3">Layout</td> + * <td><code>{@link #onMeasure}</code></td> + * <td>Called to determine the size requirements for this view and all + * of its children. + * </td> + * </tr> + * <tr> + * <td><code>{@link #onLayout}</code></td> + * <td>Called when this view should assign a size and position to all + * of its children. + * </td> + * </tr> + * <tr> + * <td><code>{@link #onSizeChanged}</code></td> + * <td>Called when the size of this view has changed. + * </td> + * </tr> + * + * <tr> + * <td>Drawing</td> + * <td><code>{@link #onDraw}</code></td> + * <td>Called when the view should render its content. + * </td> + * </tr> + * + * <tr> + * <td rowspan="4">Event processing</td> + * <td><code>{@link #onKeyDown}</code></td> + * <td>Called when a new key event occurs. + * </td> + * </tr> + * <tr> + * <td><code>{@link #onKeyUp}</code></td> + * <td>Called when a key up event occurs. + * </td> + * </tr> + * <tr> + * <td><code>{@link #onTrackballEvent}</code></td> + * <td>Called when a trackball motion event occurs. + * </td> + * </tr> + * <tr> + * <td><code>{@link #onTouchEvent}</code></td> + * <td>Called when a touch screen motion event occurs. + * </td> + * </tr> + * + * <tr> + * <td rowspan="2">Focus</td> + * <td><code>{@link #onFocusChanged}</code></td> + * <td>Called when the view gains or loses focus. + * </td> + * </tr> + * + * <tr> + * <td><code>{@link #onWindowFocusChanged}</code></td> + * <td>Called when the window containing the view gains or loses focus. + * </td> + * </tr> + * + * <tr> + * <td rowspan="3">Attaching</td> + * <td><code>{@link #onAttachedToWindow()}</code></td> + * <td>Called when the view is attached to a window. + * </td> + * </tr> + * + * <tr> + * <td><code>{@link #onDetachedFromWindow}</code></td> + * <td>Called when the view is detached from its window. + * </td> + * </tr> + * + * <tr> + * <td><code>{@link #onWindowVisibilityChanged}</code></td> + * <td>Called when the visibility of the window containing the view + * has changed. + * </td> + * </tr> + * </tbody> + * + * </table> + * </p> + * + * <a name="IDs"></a> + * <h3>IDs</h3> + * Views may have an integer id associated with them. These ids are typically + * assigned in the layout XML files, and are used to find specific views within + * the view tree. A common pattern is to: + * <ul> + * <li>Define a Button in the layout file and assign it a unique ID. + * <pre> + * <Button id="@+id/my_button" + * android:layout_width="wrap_content" + * android:layout_height="wrap_content" + * android:text="@string/my_button_text"/> + * </pre></li> + * <li>From the onCreate method of an Activity, find the Button + * <pre class="prettyprint"> + * Button myButton = (Button) findViewById(R.id.my_button); + * </pre></li> + * </ul> + * <p> + * View IDs need not be unique throughout the tree, but it is good practice to + * ensure that they are at least unique within the part of the tree you are + * searching. + * </p> + * + * <a name="Position"></a> + * <h3>Position</h3> + * <p> + * The geometry of a view is that of a rectangle. A view has a location, + * expressed as a pair of <em>left</em> and <em>top</em> coordinates, and + * two dimensions, expressed as a width and a height. The unit for location + * and dimensions is the pixel. + * </p> + * + * <p> + * It is possible to retrieve the location of a view by invoking the methods + * {@link #getLeft()} and {@link #getTop()}. The former returns the left, or X, + * coordinate of the rectangle representing the view. The latter returns the + * top, or Y, coordinate of the rectangle representing the view. These methods + * both return the location of the view relative to its parent. For instance, + * when getLeft() returns 20, that means the view is located 20 pixels to the + * right of the left edge of its direct parent. + * </p> + * + * <p> + * In addition, several convenience methods are offered to avoid unnecessary + * computations, namely {@link #getRight()} and {@link #getBottom()}. + * These methods return the coordinates of the right and bottom edges of the + * rectangle representing the view. For instance, calling {@link #getRight()} + * is similar to the following computation: <code>getLeft() + getWidth()</code> + * (see <a href="#SizePaddingMargins">Size</a> for more information about the width.) + * </p> + * + * <a name="SizePaddingMargins"></a> + * <h3>Size, padding and margins</h3> + * <p> + * The size of a view is expressed with a width and a height. A view actually + * possess two pairs of width and height values. + * </p> + * + * <p> + * The first pair is known as <em>measured width</em> and + * <em>measured height</em>. These dimensions define how big a view wants to be + * within its parent (see <a href="#Layout">Layout</a> for more details.) The + * measured dimensions can be obtained by calling {@link #getMeasuredWidth()} + * and {@link #getMeasuredHeight()}. + * </p> + * + * <p> + * The second pair is simply known as <em>width</em> and <em>height</em>, or + * sometimes <em>drawing width</em> and <em>drawing height</em>. These + * dimensions define the actual size of the view on screen, at drawing time and + * after layout. These values may, but do not have to, be different from the + * measured width and height. The width and height can be obtained by calling + * {@link #getWidth()} and {@link #getHeight()}. + * </p> + * + * <p> + * To measure its dimensions, a view takes into account its padding. The padding + * is expressed in pixels for the left, top, right and bottom parts of the view. + * Padding can be used to offset the content of the view by a specific amount of + * pixels. For instance, a left padding of 2 will push the view's content by + * 2 pixels to the right of the left edge. Padding can be set using the + * {@link #setPadding(int, int, int, int)} method and queried by calling + * {@link #getPaddingLeft()}, {@link #getPaddingTop()}, + * {@link #getPaddingRight()} and {@link #getPaddingBottom()}. + * </p> + * + * <p> + * Even though a view can define a padding, it does not provide any support for + * margins. However, view groups provide such a support. Refer to + * {@link android.view.ViewGroup} and + * {@link android.view.ViewGroup.MarginLayoutParams} for further information. + * </p> + * + * <a name="Layout"></a> + * <h3>Layout</h3> + * <p> + * Layout is a two pass process: a measure pass and a layout pass. The measuring + * pass is implemented in {@link #measure(int, int)} and is a top-down traversal + * of the view tree. Each view pushes dimension specifications down the tree + * during the recursion. At the end of the measure pass, every view has stored + * its measurements. The second pass happens in + * {@link #layout(int,int,int,int)} and is also top-down. During + * this pass each parent is responsible for positioning all of its children + * using the sizes computed in the measure pass. + * </p> + * + * <p> + * When a view's measure() method returns, its {@link #getMeasuredWidth()} and + * {@link #getMeasuredHeight()} values must be set, along with those for all of + * that view's descendants. A view's measured width and measured height values + * must respect the constraints imposed by the view's parents. This guarantees + * that at the end of the measure pass, all parents accept all of their + * children's measurements. A parent view may call measure() more than once on + * its children. For example, the parent may measure each child once with + * unspecified dimensions to find out how big they want to be, then call + * measure() on them again with actual numbers if the sum of all the children's + * unconstrained sizes is too big or too small. + * </p> + * + * <p> + * The measure pass uses two classes to communicate dimensions. The + * {@link MeasureSpec} class is used by views to tell their parents how they + * want to be measured and positioned. The base LayoutParams class just + * describes how big the view wants to be for both width and height. For each + * dimension, it can specify one of: + * <ul> + * <li> an exact number + * <li>FILL_PARENT, which means the view wants to be as big as its parent + * (minus padding) + * <li> WRAP_CONTENT, which means that the view wants to be just big enough to + * enclose its content (plus padding). + * </ul> + * There are subclasses of LayoutParams for different subclasses of ViewGroup. + * For example, AbsoluteLayout has its own subclass of LayoutParams which adds + * an X and Y value. + * </p> + * + * <p> + * MeasureSpecs are used to push requirements down the tree from parent to + * child. A MeasureSpec can be in one of three modes: + * <ul> + * <li>UNSPECIFIED: This is used by a parent to determine the desired dimension + * of a child view. For example, a LinearLayout may call measure() on its child + * with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how + * tall the child view wants to be given a width of 240 pixels. + * <li>EXACTLY: This is used by the parent to impose an exact size on the + * child. The child must use this size, and guarantee that all of its + * descendants will fit within this size. + * <li>AT_MOST: This is used by the parent to impose a maximum size on the + * child. The child must gurantee that it and all of its descendants will fit + * within this size. + * </ul> + * </p> + * + * <p> + * To intiate a layout, call {@link #requestLayout}. This method is typically + * called by a view on itself when it believes that is can no longer fit within + * its current bounds. + * </p> + * + * <a name="Drawing"></a> + * <h3>Drawing</h3> + * <p> + * Drawing is handled by walking the tree and rendering each view that + * intersects the the invalid region. Because the tree is traversed in-order, + * this means that parents will draw before (i.e., behind) their children, with + * siblings drawn in the order they appear in the tree. + * </p> + * + * <p> + * The framework will not draw views that are not in the invalid region, and also + * will take care of drawing the views background for you. + * </p> + * + * <p> + * To force a view to draw, call {@link #invalidate()}. + * </p> + * + * <a name="EventHandlingThreading"></a> + * <h3>Event Handling and Threading</h3> + * <p> + * The basic cycle of a view is as follows: + * <ol> + * <li>An event comes in and is dispatched to the appropriate view. The view + * handles the event and notifies any listeners.</li> + * <li>If in the course of processing the event, the view's bounds may need + * to be changed, the view will call {@link #requestLayout()}.</li> + * <li>Similarly, if in the course of processing the event the view's appearance + * may need to be changed, the view will call {@link #invalidate()}.</li> + * <li>If either {@link #requestLayout()} or {@link #invalidate()} were called, + * the framework will take care of measuring, laying out, and drawing the tree + * as appropriate.</li> + * </ol> + * </p> + * + * <p><em>Note: The entire view tree is single threaded. You must always be on + * the UI thread when calling any method on any view.</em> + * If you are doing work on other threads and want to update the state of a view + * from that thread, you should use a {@link Handler}. + * </p> + * + * <a name="FocusHandling"></a> + * <h3>Focus Handling</h3> + * <p> + * The framework will handle routine focus movement in response to user input. + * This includes changing the focus as views are removed or hidden, or as new + * views become available. Views indicate their willingness to take focus + * through the {@link #isFocusable} method. To change whether a view can take + * focus, call {@link #setFocusable(boolean)}. When in touch mode (see notes below) + * views indicate whether they still would like focus via {@link #isFocusableInTouchMode} + * and can change this via {@link #setFocusableInTouchMode(boolean)}. + * </p> + * <p> + * Focus movement is based on an algorithm which finds the nearest neighbor in a + * given direction. In rare cases, the default algorithm may not match the + * intended behavior of the developer. In these situations, you can provide + * explicit overrides by using these XML attributes in the layout file: + * <pre> + * nextFocusDown + * nextFocusLeft + * nextFocusRight + * nextFocusUp + * </pre> + * </p> + * + * + * <p> + * To get a particular view to take focus, call {@link #requestFocus()}. + * </p> + * + * <a name="TouchMode"></a> + * <h3>Touch Mode</h3> + * <p> + * When a user is navigating a user interface via directional keys such as a D-pad, it is + * necessary to give focus to actionable items such as buttons so the user can see + * what will take input. If the device has touch capabilities, however, and the user + * begins interacting with the interface by touching it, it is no longer necessary to + * always highlight, or give focus to, a particular view. This motivates a mode + * for interaction named 'touch mode'. + * </p> + * <p> + * For a touch capable device, once the user touches the screen, the device + * will enter touch mode. From this point onward, only views for which + * {@link #isFocusableInTouchMode} is true will be focusable, such as text editing widgets. + * Other views that are touchable, like buttons, will not take focus when touched; they will + * only fire the on click listeners. + * </p> + * <p> + * Any time a user hits a directional key, such as a D-pad direction, the view device will + * exit touch mode, and find a view to take focus, so that the user may resume interacting + * with the user interface without touching the screen again. + * </p> + * <p> + * The touch mode state is maintained across {@link android.app.Activity}s. Call + * {@link #isInTouchMode} to see whether the device is currently in touch mode. + * </p> + * + * <a name="Scrolling"></a> + * <h3>Scrolling</h3> + * <p> + * The framework provides basic support for views that wish to internally + * scroll their content. This includes keeping track of the X and Y scroll + * offset as well as mechanisms for drawing scrollbars. See + * {@link #scrollBy(int, int)}, {@link #scrollTo(int, int)} for more details. + * </p> + * + * <a name="Tags"></a> + * <h3>Tags</h3> + * <p> + * Unlike IDs, tags are not used to identify views. Tags are essentially an + * extra piece of information that can be associated with a view. They are most + * often used as a convenience to store data related to views in the views + * themselves rather than by putting them in a separate structure. + * </p> + * + * <a name="Animation"></a> + * <h3>Animation</h3> + * <p> + * You can attach an {@link Animation} object to a view using + * {@link #setAnimation(Animation)} or + * {@link #startAnimation(Animation)}. The animation can alter the scale, + * rotation, translation and alpha of a view over time. If the animation is + * attached to a view that has children, the animation will affect the entire + * subtree rooted by that node. When an animation is started, the framework will + * take care of redrawing the appropriate views until the animation completes. + * </p> + * + * @attr ref android.R.styleable#View_fitsSystemWindows + * @attr ref android.R.styleable#View_nextFocusDown + * @attr ref android.R.styleable#View_nextFocusLeft + * @attr ref android.R.styleable#View_nextFocusRight + * @attr ref android.R.styleable#View_nextFocusUp + * @attr ref android.R.styleable#View_scrollX + * @attr ref android.R.styleable#View_scrollY + * @attr ref android.R.styleable#View_scrollbarTrackHorizontal + * @attr ref android.R.styleable#View_scrollbarThumbHorizontal + * @attr ref android.R.styleable#View_scrollbarSize + * @attr ref android.R.styleable#View_scrollbars + * @attr ref android.R.styleable#View_scrollbarThumbVertical + * @attr ref android.R.styleable#View_scrollbarTrackVertical + * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack + * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack + * + * @see android.view.ViewGroup + */ +public class View implements Drawable.Callback, KeyEvent.Callback { + private static final boolean DBG = false; + + /** + * The logging tag used by this class with android.util.Log. + */ + protected static final String VIEW_LOG_TAG = "View"; + + /** + * Used to mark a View that has no ID. + */ + public static final int NO_ID = -1; + + /** + * This view does not want keystrokes. Use with TAKES_FOCUS_MASK when + * calling setFlags. + */ + private static final int NOT_FOCUSABLE = 0x00000000; + + /** + * This view wants keystrokes. Use with TAKES_FOCUS_MASK when calling + * setFlags. + */ + private static final int FOCUSABLE = 0x00000001; + + /** + * Mask for use with setFlags indicating bits used for focus. + */ + private static final int FOCUSABLE_MASK = 0x00000001; + + /** + * This view will adjust its padding to fit sytem windows (e.g. status bar) + */ + private static final int FITS_SYSTEM_WINDOWS = 0x00000002; + + /** + * This view is visible. Use with {@link #setVisibility}. + */ + public static final int VISIBLE = 0x00000000; + + /** + * This view is invisible, but it still takes up space for layout purposes. + * Use with {@link #setVisibility}. + */ + public static final int INVISIBLE = 0x00000004; + + /** + * This view is invisible, and it doesn't take any space for layout + * purposes. Use with {@link #setVisibility}. + */ + public static final int GONE = 0x00000008; + + /** + * Mask for use with setFlags indicating bits used for visibility. + * {@hide} + */ + static final int VISIBILITY_MASK = 0x0000000C; + + private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE}; + + /** + * This view is enabled. Intrepretation varies by subclass. + * Use with ENABLED_MASK when calling setFlags. + * {@hide} + */ + static final int ENABLED = 0x00000000; + + /** + * This view is disabled. Intrepretation varies by subclass. + * Use with ENABLED_MASK when calling setFlags. + * {@hide} + */ + static final int DISABLED = 0x00000020; + + /** + * Mask for use with setFlags indicating bits used for indicating whether + * this view is enabled + * {@hide} + */ + static final int ENABLED_MASK = 0x00000020; + + /** + * This view won't draw. {@link #onDraw} won't be called and further + * optimizations + * will be performed. It is okay to have this flag set and a background. + * Use with DRAW_MASK when calling setFlags. + * {@hide} + */ + static final int WILL_NOT_DRAW = 0x00000080; + + /** + * Mask for use with setFlags indicating bits used for indicating whether + * this view is will draw + * {@hide} + */ + static final int DRAW_MASK = 0x00000080; + + /** + * <p>This view doesn't show scrollbars.</p> + * {@hide} + */ + static final int SCROLLBARS_NONE = 0x00000000; + + /** + * <p>This view shows horizontal scrollbars.</p> + * {@hide} + */ + static final int SCROLLBARS_HORIZONTAL = 0x00000100; + + /** + * <p>This view shows vertical scrollbars.</p> + * {@hide} + */ + static final int SCROLLBARS_VERTICAL = 0x00000200; + + /** + * <p>Mask for use with setFlags indicating bits used for indicating which + * scrollbars are enabled.</p> + * {@hide} + */ + static final int SCROLLBARS_MASK = 0x00000300; + + // note 0x00000400 and 0x00000800 are now available for next flags... + + /** + * <p>This view doesn't show fading edges.</p> + * {@hide} + */ + static final int FADING_EDGE_NONE = 0x00000000; + + /** + * <p>This view shows horizontal fading edges.</p> + * {@hide} + */ + static final int FADING_EDGE_HORIZONTAL = 0x00001000; + + /** + * <p>This view shows vertical fading edges.</p> + * {@hide} + */ + static final int FADING_EDGE_VERTICAL = 0x00002000; + + /** + * <p>Mask for use with setFlags indicating bits used for indicating which + * fading edges are enabled.</p> + * {@hide} + */ + static final int FADING_EDGE_MASK = 0x00003000; + + /** + * <p>Indicates this view can be clicked. When clickable, a View reacts + * to clicks by notifying the OnClickListener.<p> + * {@hide} + */ + static final int CLICKABLE = 0x00004000; + + /** + * <p>Indicates this view is caching its drawing into a bitmap.</p> + * {@hide} + */ + static final int DRAWING_CACHE_ENABLED = 0x00008000; + + /** + * <p>Indicates that no icicle should be saved for this view.<p> + * {@hide} + */ + static final int SAVE_DISABLED = 0x000010000; + + /** + * <p>Mask for use with setFlags indicating bits used for the saveEnabled + * property.</p> + * {@hide} + */ + static final int SAVE_DISABLED_MASK = 0x000010000; + + /** + * <p>Indicates that no drawing cache should ever be created for this view.<p> + * {@hide} + */ + static final int WILL_NOT_CACHE_DRAWING = 0x000020000; + + /** + * <p>Indicates this view can take / keep focus when int touch mode.</p> + * {@hide} + */ + static final int FOCUSABLE_IN_TOUCH_MODE = 0x00040000; + + /** + * <p>Enables low quality mode for the drawing cache.</p> + */ + public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000; + + /** + * <p>Enables high quality mode for the drawing cache.</p> + */ + public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000; + + /** + * <p>Enables automatic quality mode for the drawing cache.</p> + */ + public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000; + + private static final int[] DRAWING_CACHE_QUALITY_FLAGS = { + DRAWING_CACHE_QUALITY_AUTO, DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH + }; + + /** + * <p>Mask for use with setFlags indicating bits used for the cache + * quality property.</p> + * {@hide} + */ + static final int DRAWING_CACHE_QUALITY_MASK = 0x00180000; + + /** + * <p> + * Indicates this view can be long clicked. When long clickable, a View + * reacts to long clicks by notifying the OnLongClickListener or showing a + * context menu. + * </p> + * {@hide} + */ + static final int LONG_CLICKABLE = 0x00200000; + + /** + * <p>Indicates that this view gets its drawable states from its direct parent + * and ignores its original internal states.</p> + * + * @hide + */ + static final int DUPLICATE_PARENT_STATE = 0x00400000; + + /** + * The scrollbar style to display the scrollbars inside the content area, + * without increasing the padding. The scrollbars will be overlaid with + * translucency on the view's content. + */ + public static final int SCROLLBARS_INSIDE_OVERLAY = 0; + + /** + * The scrollbar style to display the scrollbars inside the padded area, + * increasing the padding of the view. The scrollbars will not overlap the + * content area of the view. + */ + public static final int SCROLLBARS_INSIDE_INSET = 0x01000000; + + /** + * The scrollbar style to display the scrollbars at the edge of the view, + * without increasing the padding. The scrollbars will be overlaid with + * translucency. + */ + public static final int SCROLLBARS_OUTSIDE_OVERLAY = 0x02000000; + + /** + * The scrollbar style to display the scrollbars at the edge of the view, + * increasing the padding of the view. The scrollbars will only overlap the + * background, if any. + */ + public static final int SCROLLBARS_OUTSIDE_INSET = 0x03000000; + + /** + * Mask to check if the scrollbar style is overlay or inset. + * {@hide} + */ + static final int SCROLLBARS_INSET_MASK = 0x01000000; + + /** + * Mask to check if the scrollbar style is inside or outside. + * {@hide} + */ + static final int SCROLLBARS_OUTSIDE_MASK = 0x02000000; + + /** + * Mask for scrollbar style. + * {@hide} + */ + static final int SCROLLBARS_STYLE_MASK = 0x03000000; + + /** + * View flag indicating that the screen should remain on while the + * window containing this view is visible to the user. This effectively + * takes care of automatically setting the WindowManager's + * {@link WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}. + */ + public static final int KEEP_SCREEN_ON = 0x04000000; + + /** + * View flag indicating whether this view should have sound effects enabled + * for events such as clicking and touching. + */ + public static final int SOUND_EFFECTS_ENABLED = 0x08000000; + + /** + * Use with {@link #focusSearch}. Move focus to the previous selectable + * item. + */ + public static final int FOCUS_BACKWARD = 0x00000001; + + /** + * Use with {@link #focusSearch}. Move focus to the next selectable + * item. + */ + public static final int FOCUS_FORWARD = 0x00000002; + + /** + * Use with {@link #focusSearch}. Move focus to the left. + */ + public static final int FOCUS_LEFT = 0x00000011; + + /** + * Use with {@link #focusSearch}. Move focus up. + */ + public static final int FOCUS_UP = 0x00000021; + + /** + * Use with {@link #focusSearch}. Move focus to the right. + */ + public static final int FOCUS_RIGHT = 0x00000042; + + /** + * Use with {@link #focusSearch}. Move focus down. + */ + public static final int FOCUS_DOWN = 0x00000082; + + /** + * Base View state sets + */ + // Singles + /** + * Indicates the view has no states set. States are used with + * {@link android.graphics.drawable.Drawable} to change the drawing of the + * view depending on its state. + * + * @see android.graphics.drawable.Drawable + * @see #getDrawableState() + */ + protected static final int[] EMPTY_STATE_SET = {}; + /** + * Indicates the view is enabled. States are used with + * {@link android.graphics.drawable.Drawable} to change the drawing of the + * view depending on its state. + * + * @see android.graphics.drawable.Drawable + * @see #getDrawableState() + */ + protected static final int[] ENABLED_STATE_SET = {R.attr.state_enabled}; + /** + * Indicates the view is focused. States are used with + * {@link android.graphics.drawable.Drawable} to change the drawing of the + * view depending on its state. + * + * @see android.graphics.drawable.Drawable + * @see #getDrawableState() + */ + protected static final int[] FOCUSED_STATE_SET = {R.attr.state_focused}; + /** + * Indicates the view is selected. States are used with + * {@link android.graphics.drawable.Drawable} to change the drawing of the + * view depending on its state. + * + * @see android.graphics.drawable.Drawable + * @see #getDrawableState() + */ + protected static final int[] SELECTED_STATE_SET = {R.attr.state_selected}; + /** + * Indicates the view is pressed. States are used with + * {@link android.graphics.drawable.Drawable} to change the drawing of the + * view depending on its state. + * + * @see android.graphics.drawable.Drawable + * @see #getDrawableState() + * @hide + */ + protected static final int[] PRESSED_STATE_SET = {R.attr.state_pressed}; + /** + * Indicates the view's window has focus. States are used with + * {@link android.graphics.drawable.Drawable} to change the drawing of the + * view depending on its state. + * + * @see android.graphics.drawable.Drawable + * @see #getDrawableState() + */ + protected static final int[] WINDOW_FOCUSED_STATE_SET = + {R.attr.state_window_focused}; + // Doubles + /** + * Indicates the view is enabled and has the focus. + * + * @see #ENABLED_STATE_SET + * @see #FOCUSED_STATE_SET + */ + protected static final int[] ENABLED_FOCUSED_STATE_SET = + stateSetUnion(ENABLED_STATE_SET, FOCUSED_STATE_SET); + /** + * Indicates the view is enabled and selected. + * + * @see #ENABLED_STATE_SET + * @see #SELECTED_STATE_SET + */ + protected static final int[] ENABLED_SELECTED_STATE_SET = + stateSetUnion(ENABLED_STATE_SET, SELECTED_STATE_SET); + /** + * Indicates the view is enabled and that its window has focus. + * + * @see #ENABLED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + /** + * Indicates the view is focused and selected. + * + * @see #FOCUSED_STATE_SET + * @see #SELECTED_STATE_SET + */ + protected static final int[] FOCUSED_SELECTED_STATE_SET = + stateSetUnion(FOCUSED_STATE_SET, SELECTED_STATE_SET); + /** + * Indicates the view has the focus and that its window has the focus. + * + * @see #FOCUSED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + /** + * Indicates the view is selected and that its window has the focus. + * + * @see #SELECTED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + // Triples + /** + * Indicates the view is enabled, focused and selected. + * + * @see #ENABLED_STATE_SET + * @see #FOCUSED_STATE_SET + * @see #SELECTED_STATE_SET + */ + protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET = + stateSetUnion(ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET); + /** + * Indicates the view is enabled, focused and its window has the focus. + * + * @see #ENABLED_STATE_SET + * @see #FOCUSED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + /** + * Indicates the view is enabled, selected and its window has the focus. + * + * @see #ENABLED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + /** + * Indicates the view is focused, selected and its window has the focus. + * + * @see #FOCUSED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + /** + * Indicates the view is enabled, focused, selected and its window + * has the focus. + * + * @see #ENABLED_STATE_SET + * @see #FOCUSED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(ENABLED_FOCUSED_SELECTED_STATE_SET, + WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed and its window has the focus. + * + * @see #PRESSED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed and selected. + * + * @see #PRESSED_STATE_SET + * @see #SELECTED_STATE_SET + */ + protected static final int[] PRESSED_SELECTED_STATE_SET = + stateSetUnion(PRESSED_STATE_SET, SELECTED_STATE_SET); + + /** + * Indicates the view is pressed, selected and its window has the focus. + * + * @see #PRESSED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed and focused. + * + * @see #PRESSED_STATE_SET + * @see #FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_STATE_SET, FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed, focused and its window has the focus. + * + * @see #PRESSED_STATE_SET + * @see #FOCUSED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed, focused and selected. + * + * @see #PRESSED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_FOCUSED_SELECTED_STATE_SET = + stateSetUnion(PRESSED_FOCUSED_STATE_SET, SELECTED_STATE_SET); + + /** + * Indicates the view is pressed, focused, selected and its window has the focus. + * + * @see #PRESSED_STATE_SET + * @see #FOCUSED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed and enabled. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_STATE_SET = + stateSetUnion(PRESSED_STATE_SET, ENABLED_STATE_SET); + + /** + * Indicates the view is pressed, enabled and its window has the focus. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed, enabled and selected. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + * @see #SELECTED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_SELECTED_STATE_SET = + stateSetUnion(PRESSED_ENABLED_STATE_SET, SELECTED_STATE_SET); + + /** + * Indicates the view is pressed, enabled, selected and its window has the + * focus. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed, enabled and focused. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + * @see #FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_ENABLED_STATE_SET, FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed, enabled, focused and its window has the + * focus. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + * @see #FOCUSED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * Indicates the view is pressed, enabled, focused and selected. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = + stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET); + + /** + * Indicates the view is pressed, enabled, focused, selected and its window + * has the focus. + * + * @see #PRESSED_STATE_SET + * @see #ENABLED_STATE_SET + * @see #SELECTED_STATE_SET + * @see #FOCUSED_STATE_SET + * @see #WINDOW_FOCUSED_STATE_SET + */ + protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = + stateSetUnion(PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + /** + * The order here is very important to {@link #getDrawableState()} + */ + private static final int[][] VIEW_STATE_SETS = { + EMPTY_STATE_SET, // 0 0 0 0 0 + WINDOW_FOCUSED_STATE_SET, // 0 0 0 0 1 + SELECTED_STATE_SET, // 0 0 0 1 0 + SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 0 0 1 1 + FOCUSED_STATE_SET, // 0 0 1 0 0 + FOCUSED_WINDOW_FOCUSED_STATE_SET, // 0 0 1 0 1 + FOCUSED_SELECTED_STATE_SET, // 0 0 1 1 0 + FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 0 1 1 1 + ENABLED_STATE_SET, // 0 1 0 0 0 + ENABLED_WINDOW_FOCUSED_STATE_SET, // 0 1 0 0 1 + ENABLED_SELECTED_STATE_SET, // 0 1 0 1 0 + ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 1 0 1 1 + ENABLED_FOCUSED_STATE_SET, // 0 1 1 0 0 + ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET, // 0 1 1 0 1 + ENABLED_FOCUSED_SELECTED_STATE_SET, // 0 1 1 1 0 + ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 1 1 1 1 + PRESSED_STATE_SET, // 1 0 0 0 0 + PRESSED_WINDOW_FOCUSED_STATE_SET, // 1 0 0 0 1 + PRESSED_SELECTED_STATE_SET, // 1 0 0 1 0 + PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 0 0 1 1 + PRESSED_FOCUSED_STATE_SET, // 1 0 1 0 0 + PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET, // 1 0 1 0 1 + PRESSED_FOCUSED_SELECTED_STATE_SET, // 1 0 1 1 0 + PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 0 1 1 1 + PRESSED_ENABLED_STATE_SET, // 1 1 0 0 0 + PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET, // 1 1 0 0 1 + PRESSED_ENABLED_SELECTED_STATE_SET, // 1 1 0 1 0 + PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 1 0 1 1 + PRESSED_ENABLED_FOCUSED_STATE_SET, // 1 1 1 0 0 + PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET, // 1 1 1 0 1 + PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET, // 1 1 1 1 0 + PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 1 1 1 1 + }; + + /** + * Used by views that contain lists of items. This state indicates that + * the view is showing the last item. + * @hide + */ + protected static final int[] LAST_STATE_SET = {R.attr.state_last}; + /** + * Used by views that contain lists of items. This state indicates that + * the view is showing the first item. + * @hide + */ + protected static final int[] FIRST_STATE_SET = {R.attr.state_first}; + /** + * Used by views that contain lists of items. This state indicates that + * the view is showing the middle item. + * @hide + */ + protected static final int[] MIDDLE_STATE_SET = {R.attr.state_middle}; + /** + * Used by views that contain lists of items. This state indicates that + * the view is showing only one item. + * @hide + */ + protected static final int[] SINGLE_STATE_SET = {R.attr.state_single}; + /** + * Used by views that contain lists of items. This state indicates that + * the view is pressed and showing the last item. + * @hide + */ + protected static final int[] PRESSED_LAST_STATE_SET = {R.attr.state_last, R.attr.state_pressed}; + /** + * Used by views that contain lists of items. This state indicates that + * the view is pressed and showing the first item. + * @hide + */ + protected static final int[] PRESSED_FIRST_STATE_SET = {R.attr.state_first, R.attr.state_pressed}; + /** + * Used by views that contain lists of items. This state indicates that + * the view is pressed and showing the middle item. + * @hide + */ + protected static final int[] PRESSED_MIDDLE_STATE_SET = {R.attr.state_middle, R.attr.state_pressed}; + /** + * Used by views that contain lists of items. This state indicates that + * the view is pressed and showing only one item. + * @hide + */ + protected static final int[] PRESSED_SINGLE_STATE_SET = {R.attr.state_single, R.attr.state_pressed}; + + /** + * The animation currently associated with this view. + * @hide + */ + protected Animation mCurrentAnimation = null; + + /** + * Width as measured during measure pass. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mMeasuredWidth; + + /** + * Height as measured during measure pass. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mMeasuredHeight; + + /** + * Used to store a pair of coordinates, for instance returned values + * returned by {@link #getLocationInWindow(int[])}. + * + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected final int[] mLocation = new int[2]; + + /** + * The view's identifier. + * {@hide} + * + * @see #setId(int) + * @see #getId() + */ + @ViewDebug.ExportedProperty(resolveId = true) + int mID = NO_ID; + + /** + * The view's tag. + * {@hide} + * + * @see #setTag(Object) + * @see #getTag() + */ + protected Object mTag; + + // for mPrivateFlags: + /** {@hide} */ + static final int WANTS_FOCUS = 0x00000001; + /** {@hide} */ + static final int FOCUSED = 0x00000002; + /** {@hide} */ + static final int SELECTED = 0x00000004; + /** {@hide} */ + static final int IS_ROOT_NAMESPACE = 0x00000008; + /** {@hide} */ + static final int HAS_BOUNDS = 0x00000010; + /** {@hide} */ + static final int DRAWN = 0x00000020; + /** {@hide} */ + static final int SKIP_DRAW = 0x00000080; + /** {@hide} */ + static final int ONLY_DRAWS_BACKGROUND = 0x00000100; + /** {@hide} */ + static final int REQUEST_TRANSPARENT_REGIONS = 0x00000200; + /** {@hide} */ + static final int DRAWABLE_STATE_DIRTY = 0x00000400; + /** {@hide} */ + static final int MEASURED_DIMENSION_SET = 0x00000800; + /** {@hide} */ + static final int FORCE_LAYOUT = 0x00001000; + + private static final int LAYOUT_REQUIRED = 0x00002000; + + private static final int PRESSED = 0x00004000; + + /** {@hide} */ + static final int DRAWING_CACHE_VALID = 0x00008000; + /** + * Flag used to indicate that this view should be drawn once more (and only once + * more) after its animation has completed. + * {@hide} + */ + static final int ANIMATION_STARTED = 0x00010000; + + private static final int SAVE_STATE_CALLED = 0x00020000; + + /** + * Indicates that the View returned true when onSetAlpha() was called and that + * the alpha must be restored. + * {@hide} + */ + static final int ALPHA_SET = 0x00040000; + + // Note: flag 0x00000040 is available + + /** + * The parent this view is attached to. + * {@hide} + * + * @see #getParent() + */ + protected ViewParent mParent; + + /** + * {@hide} + */ + AttachInfo mAttachInfo; + + /** + * {@hide} + */ + int mPrivateFlags; + + /** + * Count of how many windows this view has been attached to. + */ + int mWindowAttachCount; + + /** + * The layout parameters associated with this view and used by the parent + * {@link android.view.ViewGroup} to determine how this view should be + * laid out. + * {@hide} + */ + protected ViewGroup.LayoutParams mLayoutParams; + + /** + * The view flags hold various views states. + * {@hide} + */ + int mViewFlags; + + /** + * The distance in pixels from the left edge of this view's parent + * to the left edge of this view. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mLeft; + /** + * The distance in pixels from the left edge of this view's parent + * to the right edge of this view. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mRight; + /** + * The distance in pixels from the top edge of this view's parent + * to the top edge of this view. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mTop; + /** + * The distance in pixels from the top edge of this view's parent + * to the bottom edge of this view. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mBottom; + + /** + * The offset, in pixels, by which the content of this view is scrolled + * horizontally. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mScrollX; + /** + * The offset, in pixels, by which the content of this view is scrolled + * vertically. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mScrollY; + + /** + * The left padding in pixels, that is the distance in pixels between the + * left edge of this view and the left edge of its content. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mPaddingLeft; + /** + * The right padding in pixels, that is the distance in pixels between the + * right edge of this view and the right edge of its content. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mPaddingRight; + /** + * The top padding in pixels, that is the distance in pixels between the + * top edge of this view and the top edge of its content. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mPaddingTop; + /** + * The bottom padding in pixels, that is the distance in pixels between the + * bottom edge of this view and the bottom edge of its content. + * {@hide} + */ + @ViewDebug.ExportedProperty + protected int mPaddingBottom; + + /** + * Cache the paddingRight set by the user to append to the scrollbar's size. + */ + @ViewDebug.ExportedProperty + int mUserPaddingRight; + + /** + * Cache the paddingBottom set by the user to append to the scrollbar's size. + */ + @ViewDebug.ExportedProperty + int mUserPaddingBottom; + + private int mOldWidthMeasureSpec = Integer.MIN_VALUE; + private int mOldHeightMeasureSpec = Integer.MIN_VALUE; + + private Resources mResources = null; + + private Drawable mBGDrawable; + + private int mBackgroundResource; + private boolean mBackgroundSizeChanged; + + /** + * Listener used to dispatch focus change events. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnFocusChangeListener mOnFocusChangeListener; + + /** + * Listener used to dispatch click events. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnClickListener mOnClickListener; + + /** + * Listener used to dispatch long click events. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnLongClickListener mOnLongClickListener; + + /** + * Listener used to build the context menu. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnCreateContextMenuListener mOnCreateContextMenuListener; + + private OnKeyListener mOnKeyListener; + + private OnTouchListener mOnTouchListener; + + /** + * The application environment this view lives in. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected Context mContext; + + private ScrollabilityCache mScrollCache; + + private int[] mDrawableState = null; + + private Bitmap mDrawingCache; + + /** + * Used for local (within a stackframe) calls that need a rect temporarily + */ + private final Rect mTempRect = new Rect(); + + /** + * When this view has focus and the next focus is {@link #FOCUS_LEFT}, + * the user may specify which view to go to next. + */ + private int mNextFocusLeftId = View.NO_ID; + + /** + * When this view has focus and the next focus is {@link #FOCUS_RIGHT}, + * the user may specify which view to go to next. + */ + private int mNextFocusRightId = View.NO_ID; + + /** + * When this view has focus and the next focus is {@link #FOCUS_UP}, + * the user may specify which view to go to next. + */ + private int mNextFocusUpId = View.NO_ID; + + /** + * When this view has focus and the next focus is {@link #FOCUS_DOWN}, + * the user may specify which view to go to next. + */ + private int mNextFocusDownId = View.NO_ID; + + private CheckForLongPress mPendingCheckForLongPress; + + /** + * Whether the long press's action has been invoked. The tap's action is invoked on the + * up event while a long press is invoked as soon as the long press duration is reached, so + * a long press could be performed before the tap is checked, in which case the tap's action + * should not be invoked. + */ + private boolean mHasPerformedLongPress; + + /** + * The minimum height of the view. We'll try our best to have the height + * of this view to at least this amount. + */ + private int mMinHeight; + + /** + * The minimum width of the view. We'll try our best to have the width + * of this view to at least this amount. + */ + private int mMinWidth; + + /** + * The delegate to handle touch events that are physically in this view + * but should be handled by another view. + */ + private TouchDelegate mTouchDelegate = null; + + /** + * Solid color to use as a background when creating the drawing cache. Enables + * the cache to use 16 bit bitmaps instead of 32 bit. + */ + private int mDrawingCacheBackgroundColor = 0; + + /** + * Special tree observer used when mAttachInfo is null. + */ + private ViewTreeObserver mFloatingTreeObserver; + + // Used for debug only + static long sInstanceCount = 0; + + /** + * Simple constructor to use when creating a view from code. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + */ + public View(Context context) { + mContext = context; + mResources = context != null ? context.getResources() : null; + ++sInstanceCount; + } + + /** + * Constructor that is called when inflating a view from XML. This is called + * when a view is being constructed from an XML file, supplying attributes + * that were specified in the XML file. This version uses a default style of + * 0, so the only attribute values applied are those in the Context's Theme + * and the given AttributeSet. + * + * <p> + * The method onFinishInflate() will be called after all children have been + * added. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @see #View(Context, AttributeSet, int) + */ + public View(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Perform inflation from XML and apply a class-specific base style. This + * constructor of View allows subclasses to use their own base style when + * they are inflating. For example, a Button class's constructor would call + * this version of the super class constructor and supply + * <code>R.attr.buttonStyle</code> for <var>defStyle</var>; this allows + * the theme's button style to modify all of the base view attributes (in + * particular its background) as well as the Button class's attributes. + * + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. If 0, no style + * will be applied (beyond what is included in the theme). This may + * either be an attribute resource, whose value will be retrieved + * from the current theme, or an explicit style resource. + * @see #View(Context, AttributeSet) + */ + public View(Context context, AttributeSet attrs, int defStyle) { + this(context); + + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, + defStyle, 0); + + Drawable background = null; + + int leftPadding = -1; + int topPadding = -1; + int rightPadding = -1; + int bottomPadding = -1; + + int padding = -1; + + int viewFlagValues = 0; + int viewFlagMasks = 0; + + int x = 0; + int y = 0; + + int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; + + viewFlagValues |= SOUND_EFFECTS_ENABLED; + viewFlagMasks |= SOUND_EFFECTS_ENABLED; + + final int N = a.getIndexCount(); + for (int i = 0; i < N; i++) { + int attr = a.getIndex(i); + switch (attr) { + case com.android.internal.R.styleable.View_background: + background = a.getDrawable(attr); + break; + case com.android.internal.R.styleable.View_padding: + padding = a.getDimensionPixelSize(attr, -1); + break; + case com.android.internal.R.styleable.View_paddingLeft: + leftPadding = a.getDimensionPixelSize(attr, -1); + break; + case com.android.internal.R.styleable.View_paddingTop: + topPadding = a.getDimensionPixelSize(attr, -1); + break; + case com.android.internal.R.styleable.View_paddingRight: + rightPadding = a.getDimensionPixelSize(attr, -1); + break; + case com.android.internal.R.styleable.View_paddingBottom: + bottomPadding = a.getDimensionPixelSize(attr, -1); + break; + case com.android.internal.R.styleable.View_scrollX: + x = a.getDimensionPixelOffset(attr, 0); + break; + case com.android.internal.R.styleable.View_scrollY: + y = a.getDimensionPixelOffset(attr, 0); + break; + case com.android.internal.R.styleable.View_id: + mID = a.getResourceId(attr, NO_ID); + break; + case com.android.internal.R.styleable.View_tag: + mTag = a.getText(attr); + break; + case com.android.internal.R.styleable.View_fitsSystemWindows: + if (a.getBoolean(attr, false)) { + viewFlagValues |= FITS_SYSTEM_WINDOWS; + viewFlagMasks |= FITS_SYSTEM_WINDOWS; + } + break; + case com.android.internal.R.styleable.View_focusable: + if (a.getBoolean(attr, false)) { + viewFlagValues |= FOCUSABLE; + viewFlagMasks |= FOCUSABLE_MASK; + } + break; + case com.android.internal.R.styleable.View_focusableInTouchMode: + if (a.getBoolean(attr, false)) { + viewFlagValues |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE; + viewFlagMasks |= FOCUSABLE_IN_TOUCH_MODE | FOCUSABLE_MASK; + } + break; + case com.android.internal.R.styleable.View_clickable: + if (a.getBoolean(attr, false)) { + viewFlagValues |= CLICKABLE; + viewFlagMasks |= CLICKABLE; + } + break; + case com.android.internal.R.styleable.View_longClickable: + if (a.getBoolean(attr, false)) { + viewFlagValues |= LONG_CLICKABLE; + viewFlagMasks |= LONG_CLICKABLE; + } + break; + case com.android.internal.R.styleable.View_saveEnabled: + if (!a.getBoolean(attr, true)) { + viewFlagValues |= SAVE_DISABLED; + viewFlagMasks |= SAVE_DISABLED_MASK; + } + break; + case com.android.internal.R.styleable.View_duplicateParentState: + if (a.getBoolean(attr, false)) { + viewFlagValues |= DUPLICATE_PARENT_STATE; + viewFlagMasks |= DUPLICATE_PARENT_STATE; + } + break; + case com.android.internal.R.styleable.View_visibility: + final int visibility = a.getInt(attr, 0); + if (visibility != 0) { + viewFlagValues |= VISIBILITY_FLAGS[visibility]; + viewFlagMasks |= VISIBILITY_MASK; + } + break; + case com.android.internal.R.styleable.View_drawingCacheQuality: + final int cacheQuality = a.getInt(attr, 0); + if (cacheQuality != 0) { + viewFlagValues |= DRAWING_CACHE_QUALITY_FLAGS[cacheQuality]; + viewFlagMasks |= DRAWING_CACHE_QUALITY_MASK; + } + break; + case com.android.internal.R.styleable.View_soundEffectsEnabled: + if (!a.getBoolean(attr, true)) { + viewFlagValues &= ~SOUND_EFFECTS_ENABLED; + viewFlagMasks |= SOUND_EFFECTS_ENABLED; + } + case R.styleable.View_scrollbars: + final int scrollbars = a.getInt(attr, SCROLLBARS_NONE); + if (scrollbars != SCROLLBARS_NONE) { + viewFlagValues |= scrollbars; + viewFlagMasks |= SCROLLBARS_MASK; + initializeScrollbars(a); + } + break; + case R.styleable.View_fadingEdge: + final int fadingEdge = a.getInt(attr, FADING_EDGE_NONE); + if (fadingEdge != FADING_EDGE_NONE) { + viewFlagValues |= fadingEdge; + viewFlagMasks |= FADING_EDGE_MASK; + initializeFadingEdge(a); + } + break; + case R.styleable.View_scrollbarStyle: + scrollbarStyle = a.getInt(attr, SCROLLBARS_INSIDE_OVERLAY); + if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) { + viewFlagValues |= scrollbarStyle & SCROLLBARS_STYLE_MASK; + viewFlagMasks |= SCROLLBARS_STYLE_MASK; + } + break; + case com.android.internal.R.styleable.View_keepScreenOn: + if (a.getBoolean(attr, false)) { + viewFlagValues |= KEEP_SCREEN_ON; + viewFlagMasks |= KEEP_SCREEN_ON; + } + break; + case R.styleable.View_nextFocusLeft: + mNextFocusLeftId = a.getResourceId(attr, View.NO_ID); + break; + case R.styleable.View_nextFocusRight: + mNextFocusRightId = a.getResourceId(attr, View.NO_ID); + break; + case R.styleable.View_nextFocusUp: + mNextFocusUpId = a.getResourceId(attr, View.NO_ID); + break; + case R.styleable.View_nextFocusDown: + mNextFocusDownId = a.getResourceId(attr, View.NO_ID); + break; + case R.styleable.View_minWidth: + mMinWidth = a.getDimensionPixelSize(attr, 0); + break; + case R.styleable.View_minHeight: + mMinHeight = a.getDimensionPixelSize(attr, 0); + break; + } + } + + if (background != null) { + setBackgroundDrawable(background); + } + + if (padding >= 0) { + leftPadding = padding; + topPadding = padding; + rightPadding = padding; + bottomPadding = padding; + } + + // If the user specified the padding (either with android:padding or + // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise + // use the default padding or the padding from the background drawable + // (stored at this point in mPadding*) + setPadding(leftPadding >= 0 ? leftPadding : mPaddingLeft, + topPadding >= 0 ? topPadding : mPaddingTop, + rightPadding >= 0 ? rightPadding : mPaddingRight, + bottomPadding >= 0 ? bottomPadding : mPaddingBottom); + + if (viewFlagMasks != 0) { + setFlags(viewFlagValues, viewFlagMasks); + } + + // Needs to be called after mViewFlags is set + if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) { + recomputePadding(); + } + + if (x != 0 || y != 0) { + scrollTo(x, y); + } + + a.recycle(); + } + + /** + * Non-public constructor for use in testing + */ + View() { + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + --sInstanceCount; + } + + /** + * <p> + * Initializes the fading edges from a given set of styled attributes. This + * method should be called by subclasses that need fading edges and when an + * instance of these subclasses is created programmatically rather than + * being inflated from XML. This method is automatically called when the XML + * is inflated. + * </p> + * + * @param a the styled attributes set to initialize the fading edges from + */ + protected void initializeFadingEdge(TypedArray a) { + initScrollCache(); + + mScrollCache.fadingEdgeLength = a.getDimensionPixelSize( + R.styleable.View_fadingEdgeLength, ViewConfiguration.getFadingEdgeLength()); + } + + /** + * Returns the size of the vertical faded edges used to indicate that more + * content in this view is visible. + * + * @return The size in pixels of the vertical faded edge or 0 if vertical + * faded edges are not enabled for this view. + * @attr ref android.R.styleable#View_fadingEdgeLength + */ + public int getVerticalFadingEdgeLength() { + if (isVerticalFadingEdgeEnabled()) { + ScrollabilityCache cache = mScrollCache; + if (cache != null) { + return cache.fadingEdgeLength; + } + } + return 0; + } + + /** + * Set the size of the faded edge used to indicate that more content in this + * view is available. Will not change whether the fading edge is enabled; use + * {@link #setVerticalFadingEdgeEnabled} or {@link #setHorizontalFadingEdgeEnabled} + * to enable the fading edge for the vertical or horizontal fading edges. + * + * @param length The size in pixels of the faded edge used to indicate that more + * content in this view is visible. + */ + public void setFadingEdgeLength(int length) { + initScrollCache(); + mScrollCache.fadingEdgeLength = length; + } + + /** + * Returns the size of the horizontal faded edges used to indicate that more + * content in this view is visible. + * + * @return The size in pixels of the horizontal faded edge or 0 if horizontal + * faded edges are not enabled for this view. + * @attr ref android.R.styleable#View_fadingEdgeLength + */ + public int getHorizontalFadingEdgeLength() { + if (isHorizontalFadingEdgeEnabled()) { + ScrollabilityCache cache = mScrollCache; + if (cache != null) { + return cache.fadingEdgeLength; + } + } + return 0; + } + + /** + * Returns the width of the vertical scrollbar. + * + * @return The width in pixels of the vertical scrollbar or 0 if there + * is no vertical scrollbar. + */ + public int getVerticalScrollbarWidth() { + ScrollabilityCache cache = mScrollCache; + if (cache != null) { + ScrollBarDrawable scrollBar = cache.scrollBar; + if (scrollBar != null) { + int size = scrollBar.getSize(true); + if (size <= 0) { + size = cache.scrollBarSize; + } + return size; + } + return 0; + } + return 0; + } + + /** + * Returns the height of the horizontal scrollbar. + * + * @return The height in pixels of the horizontal scrollbar or 0 if + * there is no horizontal scrollbar. + */ + protected int getHorizontalScrollbarHeight() { + ScrollabilityCache cache = mScrollCache; + if (cache != null) { + ScrollBarDrawable scrollBar = cache.scrollBar; + if (scrollBar != null) { + int size = scrollBar.getSize(false); + if (size <= 0) { + size = cache.scrollBarSize; + } + return size; + } + return 0; + } + return 0; + } + + /** + * <p> + * Initializes the scrollbars from a given set of styled attributes. This + * method should be called by subclasses that need scrollbars and when an + * instance of these subclasses is created programmatically rather than + * being inflated from XML. This method is automatically called when the XML + * is inflated. + * </p> + * + * @param a the styled attributes set to initialize the scrollbars from + */ + protected void initializeScrollbars(TypedArray a) { + initScrollCache(); + + if (mScrollCache.scrollBar == null) { + mScrollCache.scrollBar = new ScrollBarDrawable(); + } + + mScrollCache.scrollBarSize = a.getDimensionPixelSize( + com.android.internal.R.styleable.View_scrollbarSize, + ViewConfiguration.getScrollBarSize()); + + Drawable track = a.getDrawable(R.styleable.View_scrollbarTrackHorizontal); + mScrollCache.scrollBar.setHorizontalTrackDrawable(track); + + Drawable thumb = a.getDrawable(R.styleable.View_scrollbarThumbHorizontal); + if (thumb != null) { + mScrollCache.scrollBar.setHorizontalThumbDrawable(thumb); + } + + boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack, + false); + if (alwaysDraw) { + mScrollCache.scrollBar.setAlwaysDrawHorizontalTrack(true); + } + + track = a.getDrawable(R.styleable.View_scrollbarTrackVertical); + mScrollCache.scrollBar.setVerticalTrackDrawable(track); + + thumb = a.getDrawable(R.styleable.View_scrollbarThumbVertical); + if (thumb != null) { + mScrollCache.scrollBar.setVerticalThumbDrawable(thumb); + } + + alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack, + false); + if (alwaysDraw) { + mScrollCache.scrollBar.setAlwaysDrawVerticalTrack(true); + } + + // Re-apply user/background padding so that scrollbar(s) get added + recomputePadding(); + } + + /** + * <p> + * Initalizes the scrollability cache if necessary. + * </p> + */ + private void initScrollCache() { + if (mScrollCache == null) { + mScrollCache = new ScrollabilityCache(); + } + } + + /** + * Register a callback to be invoked when focus of this view changed. + * + * @param l The callback that will run. + */ + public void setOnFocusChangeListener(OnFocusChangeListener l) { + mOnFocusChangeListener = l; + } + + /** + * Returns the focus-change callback registered for this view. + * + * @return The callback, or null if one is not registered. + */ + public OnFocusChangeListener getOnFocusChangeListener() { + return mOnFocusChangeListener; + } + + /** + * Register a callback to be invoked when this view is clicked. If this view is not + * clickable, it becomes clickable. + * + * @param l The callback that will run + * + * @see #setClickable(boolean) + */ + public void setOnClickListener(OnClickListener l) { + if (!isClickable()) { + setClickable(true); + } + mOnClickListener = l; + } + + /** + * Register a callback to be invoked when this view is clicked and held. If this view is not + * long clickable, it becomes long clickable. + * + * @param l The callback that will run + * + * @see #setLongClickable(boolean) + */ + public void setOnLongClickListener(OnLongClickListener l) { + if (!isLongClickable()) { + setLongClickable(true); + } + mOnLongClickListener = l; + } + + /** + * Register a callback to be invoked when the context menu for this view is + * being built. If this view is not long clickable, it becomes long clickable. + * + * @param l The callback that will run + * + */ + public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) { + if (!isLongClickable()) { + setLongClickable(true); + } + mOnCreateContextMenuListener = l; + } + + /** + * Call this view's OnClickListener, if it is defined. + * + * @return True there was an assigned OnClickListener that was called, false + * otherwise is returned. + */ + public boolean performClick() { + if (mOnClickListener != null) { + playSoundEffect(SoundEffectConstants.CLICK); + mOnClickListener.onClick(this); + return true; + } + + return false; + } + + /** + * Call this view's OnLongClickListener, if it is defined. Invokes the context menu + * if the OnLongClickListener did not consume the event. + * + * @return True there was an assigned OnLongClickListener that was called, false + * otherwise is returned. + */ + public boolean performLongClick() { + boolean handled = false; + if (mOnLongClickListener != null) { + handled = mOnLongClickListener.onLongClick(View.this); + } + if (!handled) { + handled = showContextMenu(); + } + return handled; + } + + /** + * Bring up the context menu for this view. + * + * @return Whether a context menu was displayed. + */ + public boolean showContextMenu() { + return getParent().showContextMenuForChild(this); + } + + /** + * Register a callback to be invoked when a key is pressed in this view. + * @param l the key listener to attach to this view + */ + public void setOnKeyListener(OnKeyListener l) { + mOnKeyListener = l; + } + + /** + * Register a callback to be invoked when a touch event is sent to this view. + * @param l the touch listener to attach to this view + */ + public void setOnTouchListener(OnTouchListener l) { + mOnTouchListener = l; + } + + /** + * Give this view focus. This will cause {@link #onFocusChanged} to be called. + * + * Note: this does not check whether this {@link View} should get focus, it just + * gives it focus no matter what. It should only be called internally by framework + * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}. + * + * @param direction values are View.FOCUS_UP, View.FOCUS_DOWN, + * View.FOCUS_LEFT or View.FOCUS_RIGHT. This is the direction which + * focus moved when requestFocus() is called. It may not always + * apply, in which case use the default View.FOCUS_DOWN. + * @param previouslyFocusedRect The rectangle of the view that had focus + * prior in this View's coordinate system. + */ + void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) { + if (DBG) { + System.out.println(this + " requestFocus()"); + } + + if ((mPrivateFlags & FOCUSED) == 0) { + mPrivateFlags |= FOCUSED; + + if (mParent != null) { + mParent.requestChildFocus(this, this); + } + + onFocusChanged(true, direction, previouslyFocusedRect); + refreshDrawableState(); + } + } + + /** + * Request that a rectangle of this view be visible on the screen, + * scrolling if necessary just enough. + * + * A View should call this if it maintains some notion of which part + * of its content is interesting. For example, a text editing view + * should call this when its cursor moves. + * + * @param rectangle The rectangle. + * @return Whether any parent scrolled. + */ + public boolean requestRectangleOnScreen(Rect rectangle) { + return requestRectangleOnScreen(rectangle, false); + } + + /** + * Request that a rectangle of this view be visible on the screen, + * scrolling if necessary just enough. + * + * A View should call this if it maintains some notion of which part + * of its content is interesting. For example, a text editing view + * should call this when its cursor moves. + * + * When <code>immediate</code> is set to true, scrolling will not be + * animated. + * + * @param rectangle The rectangle. + * @param immediate True to forbid animated scrolling, false otherwise + * @return Whether any parent scrolled. + */ + public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) { + View child = this; + ViewParent parent = mParent; + boolean scrolled = false; + while (parent instanceof ViewGroup) { + ViewGroup vgParent = (ViewGroup) parent; + scrolled |= vgParent.requestChildRectangleOnScreen(child, + rectangle, immediate); + + // offset rect so next call has the rectangle in the + // coordinate system of its direct child. + rectangle.offset(child.getLeft(), child.getTop()); + rectangle.offset(-child.getScrollX(), -child.getScrollY()); + + child = (View) parent; + parent = child.getParent(); + } + return scrolled; + } + + /** + * Called when this view wants to give up focus. This will cause + * {@link #onFocusChanged} to be called. + */ + public void clearFocus() { + if (DBG) { + System.out.println(this + " clearFocus()"); + } + + if ((mPrivateFlags & FOCUSED) != 0) { + mPrivateFlags &= ~FOCUSED; + + if (mParent != null) { + mParent.clearChildFocus(this); + } + + onFocusChanged(false, 0, null); + refreshDrawableState(); + } + } + + /** + * Called to clear the focus of a view that is about to be removed. + * Doesn't call clearChildFocus, which prevents this view from taking + * focus again before it has been removed from the parent + */ + void clearFocusForRemoval() { + if ((mPrivateFlags & FOCUSED) != 0) { + mPrivateFlags &= ~FOCUSED; + + onFocusChanged(false, 0, null); + refreshDrawableState(); + } + } + + /** + * Called internally by the view system when a new view is getting focus. + * This is what clears the old focus. + */ + void unFocus() { + if (DBG) { + System.out.println(this + " unFocus()"); + } + + if ((mPrivateFlags & FOCUSED) != 0) { + mPrivateFlags &= ~FOCUSED; + + onFocusChanged(false, 0, null); + refreshDrawableState(); + } + } + + /** + * Returns true if this view has focus iteself, or is the ancestor of the + * view that has focus. + * + * @return True if this view has or contains focus, false otherwise. + */ + @ViewDebug.ExportedProperty + public boolean hasFocus() { + return (mPrivateFlags & FOCUSED) != 0; + } + + /** + * Returns true if this view is focusable or if it contains a reachable View + * for which {@link #hasFocusable()} returns true. A "reachable hasFocusable()" + * is a View whose parents do not block descendants focus. + * + * Only {@link #VISIBLE} views are considered focusable. + * + * @return True if the view is focusable or if the view contains a focusable + * View, false otherwise. + * + * @see ViewGroup#FOCUS_BLOCK_DESCENDANTS + */ + public boolean hasFocusable() { + return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable(); + } + + /** + * Called by the view system when the focus state of this view changes. + * When the focus change event is caused by directional navigation, direction + * and previouslyFocusedRect provide insight into where the focus is coming from. + * + * @param gainFocus True if the View has focus; false otherwise. + * @param direction The direction focus has moved when requestFocus() + * is called to give this view focus. Values are + * View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or + * View.FOCUS_RIGHT. It may not always apply, in which + * case use the default. + * @param previouslyFocusedRect The rectangle, in this view's coordinate + * system, of the previously focused view. If applicable, this will be + * passed in as finer grained information about where the focus is coming + * from (in addition to direction). Will be <code>null</code> otherwise. + */ + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + if (!gainFocus) { + if (isPressed()) { + setPressed(false); + } + } + invalidate(); + if (mOnFocusChangeListener != null) { + mOnFocusChangeListener.onFocusChange(this, gainFocus); + } + } + + /** + * Returns true if this view has focus + * + * @return True if this view has focus, false otherwise. + */ + @ViewDebug.ExportedProperty + public boolean isFocused() { + return (mPrivateFlags & FOCUSED) != 0; + } + + /** + * Find the view in the hierarchy rooted at this view that currently has + * focus. + * + * @return The view that currently has focus, or null if no focused view can + * be found. + */ + public View findFocus() { + return (mPrivateFlags & FOCUSED) != 0 ? this : null; + } + + /** + * Returns the quality of the drawing cache. + * + * @return One of {@link #DRAWING_CACHE_QUALITY_AUTO}, + * {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH} + * + * @see #setDrawingCacheQuality(int) + * @see #setDrawingCacheEnabled(boolean) + * @see #isDrawingCacheEnabled() + * + * @attr ref android.R.styleable#View_drawingCacheQuality + */ + public int getDrawingCacheQuality() { + return mViewFlags & DRAWING_CACHE_QUALITY_MASK; + } + + /** + * Set the drawing cache quality of this view. This value is used only when the + * drawing cache is enabled + * + * @param quality One of {@link #DRAWING_CACHE_QUALITY_AUTO}, + * {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH} + * + * @see #getDrawingCacheQuality() + * @see #setDrawingCacheEnabled(boolean) + * @see #isDrawingCacheEnabled() + * + * @attr ref android.R.styleable#View_drawingCacheQuality + */ + public void setDrawingCacheQuality(int quality) { + setFlags(quality, DRAWING_CACHE_QUALITY_MASK); + } + + /** + * Returns whether the screen should remain on, corresponding to the current + * value of {@link #KEEP_SCREEN_ON}. + * + * @return Returns true if {@link #KEEP_SCREEN_ON} is set. + * + * @see #setKeepScreenOn(boolean) + * + * @attr ref android.R.styleable#View_keepScreenOn + */ + public boolean getKeepScreenOn() { + return (mViewFlags & KEEP_SCREEN_ON) != 0; + } + + /** + * Controls whether the screen should remain on, modifying the + * value of {@link #KEEP_SCREEN_ON}. + * + * @param keepScreenOn Supply true to set {@link #KEEP_SCREEN_ON}. + * + * @see #getKeepScreenOn() + * + * @attr ref android.R.styleable#View_keepScreenOn + */ + public void setKeepScreenOn(boolean keepScreenOn) { + setFlags(keepScreenOn ? KEEP_SCREEN_ON : 0, KEEP_SCREEN_ON); + } + + /** + * @return The user specified next focus ID. + * + * @attr ref android.R.styleable#View_nextFocusLeft + */ + public int getNextFocusLeftId() { + return mNextFocusLeftId; + } + + /** + * Set the id of the view to use for the next focus + * + * @param nextFocusLeftId + * + * @attr ref android.R.styleable#View_nextFocusLeft + */ + public void setNextFocusLeftId(int nextFocusLeftId) { + mNextFocusLeftId = nextFocusLeftId; + } + + /** + * @return The user specified next focus ID. + * + * @attr ref android.R.styleable#View_nextFocusRight + */ + public int getNextFocusRightId() { + return mNextFocusRightId; + } + + /** + * Set the id of the view to use for the next focus + * + * @param nextFocusRightId + * + * @attr ref android.R.styleable#View_nextFocusRight + */ + public void setNextFocusRightId(int nextFocusRightId) { + mNextFocusRightId = nextFocusRightId; + } + + /** + * @return The user specified next focus ID. + * + * @attr ref android.R.styleable#View_nextFocusUp + */ + public int getNextFocusUpId() { + return mNextFocusUpId; + } + + /** + * Set the id of the view to use for the next focus + * + * @param nextFocusUpId + * + * @attr ref android.R.styleable#View_nextFocusUp + */ + public void setNextFocusUpId(int nextFocusUpId) { + mNextFocusUpId = nextFocusUpId; + } + + /** + * @return The user specified next focus ID. + * + * @attr ref android.R.styleable#View_nextFocusDown + */ + public int getNextFocusDownId() { + return mNextFocusDownId; + } + + /** + * Set the id of the view to use for the next focus + * + * @param nextFocusDownId + * + * @attr ref android.R.styleable#View_nextFocusDown + */ + public void setNextFocusDownId(int nextFocusDownId) { + mNextFocusDownId = nextFocusDownId; + } + + /** + * Returns the visibility of this view and all of its ancestors + * + * @return True if this view and all of its ancestors are {@link #VISIBLE} + */ + public boolean isShown() { + View current = this; + //noinspection ConstantConditions + do { + if ((current.mViewFlags & VISIBILITY_MASK) != VISIBLE) { + return false; + } + ViewParent parent = current.mParent; + if (parent == null) { + return false; // We are not attached to the view root + } + if (parent instanceof ViewRoot) { + return true; + } + current = (View) parent; + } while (current != null); + + return false; + } + + /** + * Apply the insets for system windows to this view, if the FITS_SYSTEM_WINDOWS flag + * is set + * + * @param insets Insets for system windows + * + * @return True if this view applied the insets, false otherwise + */ + protected boolean fitSystemWindows(Rect insets) { + if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { + mPaddingLeft = insets.left; + mPaddingTop = insets.top; + mPaddingRight = insets.right; + mPaddingBottom = insets.bottom; + return true; + } + return false; + } + + /** + * Returns the visibility status for this view. + * + * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + * @attr ref android.R.styleable#View_visibility + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = 0, to = "VISIBLE"), + @ViewDebug.IntToString(from = 4, to = "INVISIBLE"), + @ViewDebug.IntToString(from = 8, to = "GONE") + }) + public int getVisibility() { + return mViewFlags & VISIBILITY_MASK; + } + + /** + * Set the enabled state of this view. + * + * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + * @attr ref android.R.styleable#View_visibility + */ + public void setVisibility(int visibility) { + setFlags(visibility, VISIBILITY_MASK); + } + + /** + * Returns the enabled status for this view. The interpretation of the + * enabled state varies by subclass. + * + * @return True if this view is enabled, false otherwise. + */ + @ViewDebug.ExportedProperty + public boolean isEnabled() { + return (mViewFlags & ENABLED_MASK) == ENABLED; + } + + /** + * Set the enabled state of this view. The interpretation of the enabled + * state varies by subclass. + * + * @param enabled True if this view is enabled, false otherwise. + */ + public void setEnabled(boolean enabled) { + setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK); + + /* + * The View most likely has to change its appearance, so refresh + * the drawable state. + */ + refreshDrawableState(); + + // Invalidate too, since the default behavior for views is to be + // be drawn at 50% alpha rather than to change the drawable. + invalidate(); + } + + /** + * Set whether this view can receive the focus. + * + * Setting this to false will also ensure that this view is not focusable + * in touch mode. + * + * @param focusable If true, this view can receive the focus. + * + * @see #setFocusableInTouchMode(boolean) + * @attr ref android.R.styleable#View_focusable + */ + public void setFocusable(boolean focusable) { + if (!focusable) { + setFlags(0, FOCUSABLE_IN_TOUCH_MODE); + } + setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK); + } + + /** + * Set whether this view can receive focus while in touch mode. + * + * Setting this to true will also ensure that this view is focusable. + * + * @param focusableInTouchMode If true, this view can receive the focus while + * in touch mode. + * + * @see #setFocusable(boolean) + * @attr ref android.R.styleable#View_focusableInTouchMode + */ + public void setFocusableInTouchMode(boolean focusableInTouchMode) { + // Focusable in touch mode should always be set before the focusable flag + // otherwise, setting the focusable flag will trigger a focusableViewAvailable() + // which, in touch mode, will not successfully request focus on this view + // because the focusable in touch mode flag is not set + setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE); + if (focusableInTouchMode) { + setFlags(FOCUSABLE, FOCUSABLE_MASK); + } + } + + /** + * Set whether this view should have sound effects enabled for events such as + * clicking and touching. + * + * You may wish to disable sound effects for a view if you already play sounds, + * for instance, a dial key that plays dtmf tones. + * + * @param soundEffectsEnabled whether sound effects are enabled for this view. + * @see #isSoundEffectsEnabled() + * @see #playSoundEffect(int) + * @attr ref android.R.styleable#View_soundEffectsEnabled + */ + public void setSoundEffectsEnabled(boolean soundEffectsEnabled) { + setFlags(soundEffectsEnabled ? SOUND_EFFECTS_ENABLED: 0, SOUND_EFFECTS_ENABLED); + } + + /** + * @return whether this view should have sound effects enabled for events such as + * clicking and touching. + * + * @see #setSoundEffectsEnabled(boolean) + * @see #playSoundEffect(int) + * @attr ref android.R.styleable#View_soundEffectsEnabled + */ + @ViewDebug.ExportedProperty + public boolean isSoundEffectsEnabled() { + return SOUND_EFFECTS_ENABLED == (mViewFlags & SOUND_EFFECTS_ENABLED); + } + + /** + * If this view doesn't do any drawing on its own, set this flag to + * allow further optimizations. By default, this flag is not set on + * View, but could be set on some View subclasses such as ViewGroup. + * + * Typically, if you override {@link #onDraw} you should clear this flag. + * + * @param willNotDraw whether or not this View draw on its own + */ + public void setWillNotDraw(boolean willNotDraw) { + setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); + } + + /** + * Returns whether or not this View draws on its own. + * + * @return true if this view has nothing to draw, false otherwise + */ + @ViewDebug.ExportedProperty + public boolean willNotDraw() { + return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW; + } + + /** + * When a View's drawing cache is enabled, drawing is redirected to an + * offscreen bitmap. Some views, like an ImageView, must be able to + * bypass this mechanism if they already draw a single bitmap, to avoid + * unnecessary usage of the memory. + * + * @param willNotCacheDrawing true if this view does not cache its + * drawing, false otherwise + */ + public void setWillNotCacheDrawing(boolean willNotCacheDrawing) { + setFlags(willNotCacheDrawing ? WILL_NOT_CACHE_DRAWING : 0, WILL_NOT_CACHE_DRAWING); + } + + /** + * Returns whether or not this View can cache its drawing or not. + * + * @return true if this view does not cache its drawing, false otherwise + */ + @ViewDebug.ExportedProperty + public boolean willNotCacheDrawing() { + return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING; + } + + /** + * Indicates whether this view reacts to click events or not. + * + * @return true if the view is clickable, false otherwise + * + * @see #setClickable(boolean) + * @attr ref android.R.styleable#View_clickable + */ + @ViewDebug.ExportedProperty + public boolean isClickable() { + return (mViewFlags & CLICKABLE) == CLICKABLE; + } + + /** + * Enables or disables click events for this view. When a view + * is clickable it will change its state to "pressed" on every click. + * Subclasses should set the view clickable to visually react to + * user's clicks. + * + * @param clickable true to make the view clickable, false otherwise + * + * @see #isClickable() + * @attr ref android.R.styleable#View_clickable + */ + public void setClickable(boolean clickable) { + setFlags(clickable ? CLICKABLE : 0, CLICKABLE); + } + + /** + * Indicates whether this view reacts to long click events or not. + * + * @return true if the view is long clickable, false otherwise + * + * @see #setLongClickable(boolean) + * @attr ref android.R.styleable#View_longClickable + */ + public boolean isLongClickable() { + return (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE; + } + + /** + * Enables or disables long click events for this view. When a view is long + * clickable it reacts to the user holding down the button for a longer + * duration than a tap. This event can either launch the listener or a + * context menu. + * + * @param longClickable true to make the view long clickable, false otherwise + * @see #isLongClickable() + * @attr ref android.R.styleable#View_longClickable + */ + public void setLongClickable(boolean longClickable) { + setFlags(longClickable ? LONG_CLICKABLE : 0, LONG_CLICKABLE); + } + + /** + * Sets the pressed that for this view. + * + * @see #isClickable() + * @see #setClickable(boolean) + * + * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts + * the View's internal state from a previously set "pressed" state. + */ + public void setPressed(boolean pressed) { + if (pressed) { + mPrivateFlags |= PRESSED; + } else { + mPrivateFlags &= ~PRESSED; + } + refreshDrawableState(); + dispatchSetPressed(pressed); + } + + /** + * Dispatch setPressed to all of this View's children. + * + * @see #setPressed(boolean) + * + * @param pressed The new pressed state + */ + protected void dispatchSetPressed(boolean pressed) { + } + + /** + * Indicates whether the view is currently in pressed state. Unless + * {@link #setPressed(boolean)} is explicitly called, only clickable views can enter + * the pressed state. + * + * @see #setPressed + * @see #isClickable() + * @see #setClickable(boolean) + * + * @return true if the view is currently pressed, false otherwise + */ + public boolean isPressed() { + return (mPrivateFlags & PRESSED) == PRESSED; + } + + /** + * Indicates whether this view will save its state (that is, + * whether its {@link #onSaveInstanceState} method will be called). + * + * @return Returns true if the view state saving is enabled, else false. + * + * @see #setSaveEnabled(boolean) + * @attr ref android.R.styleable#View_saveEnabled + */ + public boolean isSaveEnabled() { + return (mViewFlags & SAVE_DISABLED_MASK) != SAVE_DISABLED; + } + + /** + * Controls whether the saving of this view's state is + * enabled (that is, whether its {@link #onSaveInstanceState} method + * will be called). Note that even if freezing is enabled, the + * view still must have an id assigned to it (via {@link #setId setId()}) + * for its state to be saved. This flag can only disable the + * saving of this view; any child views may still have their state saved. + * + * @param enabled Set to false to <em>disable</em> state saving, or true + * (the default) to allow it. + * + * @see #isSaveEnabled() + * @see #setId(int) + * @see #onSaveInstanceState() + * @attr ref android.R.styleable#View_saveEnabled + */ + public void setSaveEnabled(boolean enabled) { + setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK); + } + + + /** + * Returns whether this View is able to take focus. + * + * @return True if this view can take focus, or false otherwise. + * @attr ref android.R.styleable#View_focusable + */ + @ViewDebug.ExportedProperty + public final boolean isFocusable() { + return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK); + } + + /** + * When a view is focusable, it may not want to take focus when in touch mode. + * For example, a button would like focus when the user is navigating via a D-pad + * so that the user can click on it, but once the user starts touching the screen, + * the button shouldn't take focus + * @return Whether the view is focusable in touch mode. + * @attr ref android.R.styleable#View_focusableInTouchMode + */ + @ViewDebug.ExportedProperty + public final boolean isFocusableInTouchMode() { + return FOCUSABLE_IN_TOUCH_MODE == (mViewFlags & FOCUSABLE_IN_TOUCH_MODE); + } + + /** + * Find the nearest view in the specified direction that can take focus. + * This does not actually give focus to that view. + * + * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT + * + * @return The nearest focusable in the specified direction, or null if none + * can be found. + */ + public View focusSearch(int direction) { + if (mParent != null) { + return mParent.focusSearch(this, direction); + } else { + return null; + } + } + + /** + * This method is the last chance for the focused view and its ancestors to + * respond to an arrow key. This is called when the focused view did not + * consume the key internally, nor could the view system find a new view in + * the requested direction to give focus to. + * + * @param focused The currently focused view. + * @param direction The direction focus wants to move. One of FOCUS_UP, + * FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT. + * @return True if the this view consumed this unhandled move. + */ + public boolean dispatchUnhandledMove(View focused, int direction) { + return false; + } + + /** + * If a user manually specified the next view id for a particular direction, + * use the root to look up the view. Once a view is found, it is cached + * for future lookups. + * @param root The root view of the hierarchy containing this view. + * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT + * @return The user specified next view, or null if there is none. + */ + View findUserSetNextFocus(View root, int direction) { + switch (direction) { + case FOCUS_LEFT: + if (mNextFocusLeftId == View.NO_ID) return null; + return findViewShouldExist(root, mNextFocusLeftId); + case FOCUS_RIGHT: + if (mNextFocusRightId == View.NO_ID) return null; + return findViewShouldExist(root, mNextFocusRightId); + case FOCUS_UP: + if (mNextFocusUpId == View.NO_ID) return null; + return findViewShouldExist(root, mNextFocusUpId); + case FOCUS_DOWN: + if (mNextFocusDownId == View.NO_ID) return null; + return findViewShouldExist(root, mNextFocusDownId); + } + return null; + } + + private static View findViewShouldExist(View root, int childViewId) { + View result = root.findViewById(childViewId); + if (result == null) { + Log.w(VIEW_LOG_TAG, "couldn't find next focus view specified " + + "by user for id " + childViewId); + } + return result; + } + + /** + * Find and return all focusable views that are descendants of this view, + * possibly including this view if it is focusable itself. + * + * @param direction The direction of the focus + * @return A list of focusable views + */ + public ArrayList<View> getFocusables(int direction) { + ArrayList<View> result = new ArrayList<View>(24); + addFocusables(result, direction); + return result; + } + + /** + * Add any focusable views that are descendants of this view (possibly + * including this view if it is focusable itself) to views. If we are in touch mode, + * only add views that are also focusable in touch mode. + * + * @param views Focusable views found so far + * @param direction The direction of the focus + */ + public void addFocusables(ArrayList<View> views, int direction) { + if (!isFocusable()) return; + + if (isInTouchMode() && !isFocusableInTouchMode()) return; + + views.add(this); + } + + /** + * Find and return all touchable views that are descendants of this view, + * possibly including this view if it is touchable itself. + * + * @return A list of touchable views + */ + public ArrayList<View> getTouchables() { + ArrayList<View> result = new ArrayList<View>(); + addTouchables(result); + return result; + } + + /** + * Add any touchable views that are descendants of this view (possibly + * including this view if it is touchable itself) to views. + * + * @param views Touchable views found so far + */ + public void addTouchables(ArrayList<View> views) { + final int viewFlags = mViewFlags; + + if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) + && (viewFlags & ENABLED_MASK) == ENABLED) { + views.add(this); + } + } + + /** + * Call this to try to give focus to a specific view or to one of its + * descendants. + * + * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns false), + * or if it is focusable and it is not focusable in touch mode ({@link #isFocusableInTouchMode}) + * while the device is in touch mode. + * + * See also {@link #focusSearch}, which is what you call to say that you + * have focus, and you want your parent to look for the next one. + * + * This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments + * {@link #FOCUS_DOWN} and <code>null</code>. + * + * @return Whether this view or one of its descendants actually took focus. + */ + public final boolean requestFocus() { + return requestFocus(View.FOCUS_DOWN); + } + + + /** + * Call this to try to give focus to a specific view or to one of its + * descendants and give it a hint about what direction focus is heading. + * + * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns false), + * or if it is focusable and it is not focusable in touch mode ({@link #isFocusableInTouchMode}) + * while the device is in touch mode. + * + * See also {@link #focusSearch}, which is what you call to say that you + * have focus, and you want your parent to look for the next one. + * + * This is equivalent to calling {@link #requestFocus(int, Rect)} with + * <code>null</code> set for the previously focused rectangle. + * + * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT + * @return Whether this view or one of its descendants actually took focus. + */ + public final boolean requestFocus(int direction) { + return requestFocus(direction, null); + } + + /** + * Call this to try to give focus to a specific view or to one of its descendants + * and give it hints about the direction and a specific rectangle that the focus + * is coming from. The rectangle can help give larger views a finer grained hint + * about where focus is coming from, and therefore, where to show selection, or + * forward focus change internally. + * + * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns false), + * or if it is focusable and it is not focusable in touch mode ({@link #isFocusableInTouchMode}) + * while the device is in touch mode. + * + * A View will not take focus if it is not visible. + * + * A View will not take focus if one of its parents has {@link android.view.ViewGroup#getDescendantFocusability()} + * equal to {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}. + * + * See also {@link #focusSearch}, which is what you call to say that you + * have focus, and you want your parent to look for the next one. + * + * You may wish to override this method if your custom {@link View} has an internal + * {@link View} that it wishes to forward the request to. + * + * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT + * @param previouslyFocusedRect The rectangle (in this View's coordinate system) + * to give a finer grained hint about where focus is coming from. May be null + * if there is no hint. + * @return Whether this view or one of its descendants actually took focus. + */ + public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + // need to be focusable + if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE || + (mViewFlags & VISIBILITY_MASK) != VISIBLE) { + return false; + } + + // need to be focusable in touch mode if in touch mode + if (isInTouchMode() && + (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) { + return false; + } + + // need to not have any parents blocking us + if (hasAncestorThatBlocksDescendantFocus()) { + return false; + } + + handleFocusGainInternal(direction, previouslyFocusedRect); + return true; + } + + /** + * Call this to try to give focus to a specific view or to one of its descendants. This is a + * special variant of {@link #requestFocus() } that will allow views that are not focuable in + * touch mode to request focus when they are touched. + * + * @return Whether this view or one of its descendants actually took focus. + * + * @see #isInTouchMode() + * + */ + public final boolean requestFocusFromTouch() { + // Leave touch mode if we need to + if (isInTouchMode()) { + View root = getRootView(); + if (root != null) { + ViewRoot viewRoot = (ViewRoot)root.getParent(); + if (viewRoot != null) { + viewRoot.ensureTouchMode(false); + } + } + } + return requestFocus(View.FOCUS_DOWN); + } + + /** + * @return Whether any ancestor of this view blocks descendant focus. + */ + private boolean hasAncestorThatBlocksDescendantFocus() { + ViewParent ancestor = mParent; + while (ancestor instanceof ViewGroup) { + final ViewGroup vgAncestor = (ViewGroup) ancestor; + if (vgAncestor.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) { + return true; + } else { + ancestor = vgAncestor.getParent(); + } + } + return false; + } + + /** + * Dispatch a key event to the next view on the focus path. This path runs + * from the top of the view tree down to the currently focused view. If this + * view has focus, it will dispatch to itself. Otherwise it will dispatch + * the next node down the focus path. This method also fires any key + * listeners. + * + * @param event The key event to be dispatched. + * @return True if the event was handled, false otherwise. + */ + public boolean dispatchKeyEvent(KeyEvent event) { + // If any attached key listener a first crack at the event. + //noinspection SimplifiableIfStatement + if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED + && mOnKeyListener.onKey(this, event.getKeyCode(), event)) { + return true; + } + + return event.dispatch(this); + } + + /** + * Dispatches a key shortcut event. + * + * @param event The key event to be dispatched. + * @return True if the event was handled by the view, false otherwise. + */ + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return onKeyShortcut(event.getKeyCode(), event); + } + + /** + * Pass the touch screen motion event down to the target view, or this + * view if it is the target. + * + * @param event The motion event to be dispatched. + * @return True if the event was handled by the view, false otherwise. + */ + public boolean dispatchTouchEvent(MotionEvent event) { + if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && + mOnTouchListener.onTouch(this, event)) { + return true; + } + return onTouchEvent(event); + } + + /** + * Pass a trackball motion event down to the focused view. + * + * @param event The motion event to be dispatched. + * @return True if the event was handled by the view, false otherwise. + */ + public boolean dispatchTrackballEvent(MotionEvent event) { + //Log.i("view", "view=" + this + ", " + event.toString()); + return onTrackballEvent(event); + } + + /** + * Called when the window containing this view gains or loses window focus. + * ViewGroups should override to route to their children. + * + * @param hasFocus True if the window containing this view now has focus, + * false otherwise. + */ + public void dispatchWindowFocusChanged(boolean hasFocus) { + onWindowFocusChanged(hasFocus); + } + + /** + * Called when the window containing this view gains or loses focus. Note + * that this is separate from view focus: to receive key events, both + * your view and its window must have focus. If a window is displayed + * on top of yours that takes input focus, then your own window will lose + * focus but the view focus will remain unchanged. + * + * @param hasWindowFocus True if the window containing this view now has + * focus, false otherwise. + */ + public void onWindowFocusChanged(boolean hasWindowFocus) { + if (!hasWindowFocus) { + if (isPressed()) { + setPressed(false); + } + } + refreshDrawableState(); + } + + /** + * Returns true if this view is in a window that currently has window focus. + * Note that this is not the same as the view itself having focus. + * + * @return True if this view is in a window that currently has window focus. + */ + public boolean hasWindowFocus() { + return mAttachInfo != null && mAttachInfo.mHasWindowFocus; + } + + /** + * Dispatch a window visibility change down the view hierarchy. + * ViewGroups should override to route to their children. + * + * @param visibility The new visibility of the window. + * + * @see #onWindowVisibilityChanged + */ + public void dispatchWindowVisibilityChanged(int visibility) { + onWindowVisibilityChanged(visibility); + } + + /** + * Called when the window containing has change its visibility + * (between {@link #GONE}, {@link #INVISIBLE}, and {@link #VISIBLE}). Note + * that this tells you whether or not your window is being made visible + * to the window manager; this does <em>not</em> tell you whether or not + * your window is obscured by other windows on the screen, even if it + * is itself visible. + * + * @param visibility The new visibility of the window. + */ + protected void onWindowVisibilityChanged(int visibility) { + } + + /** + * Returns the current visibility of the window this view is attached to + * (either {@link #GONE}, {@link #INVISIBLE}, or {@link #VISIBLE}). + * + * @return Returns the current visibility of the view's window. + */ + public int getWindowVisibility() { + return mAttachInfo != null ? mAttachInfo.mWindowVisibility : GONE; + } + + /** + * Private function to aggregate all per-view attributes in to the view + * root. + */ + void dispatchCollectViewAttributes(int visibility) { + performCollectViewAttributes(visibility); + } + + void performCollectViewAttributes(int visibility) { + if (((visibility | mViewFlags) & (VISIBILITY_MASK | KEEP_SCREEN_ON)) + == (VISIBLE | KEEP_SCREEN_ON)) { + mAttachInfo.mKeepScreenOn = true; + } + } + + void needGlobalAttributesUpdate(boolean force) { + AttachInfo ai = mAttachInfo; + if (ai != null) { + if (ai.mKeepScreenOn || force) { + ai.mRecomputeGlobalAttributes = true; + } + } + } + + /** + * Returns whether the device is currently in touch mode. Touch mode is entered + * once the user begins interacting with the device by touch, and affects various + * things like whether focus is always visible to the user. + * + * @return Whether the device is in touch mode. + */ + @ViewDebug.ExportedProperty + public boolean isInTouchMode() { + if (mAttachInfo != null) { + return mAttachInfo.mInTouchMode; + } else { + return ViewRoot.isInTouchMode(); + } + } + + /** + * Returns the context the view is running in, through which it can + * access the current theme, resources, etc. + * + * @return The view's Context. + */ + public final Context getContext() { + return mContext; + } + + /** + * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) + * KeyEvent.Callback.onKeyMultiple()}: perform press of the view + * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER} + * is released, if the view is enabled and clickable. + * + * @param keyCode A key code that represents the button pressed, from + * {@link android.view.KeyEvent}. + * @param event The KeyEvent object that defines the button action. + */ + public boolean onKeyDown(int keyCode, KeyEvent event) { + boolean result = false; + + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: { + if ((mViewFlags & ENABLED_MASK) == DISABLED) { + return true; + } + // Long clickable items don't necessarily have to be clickable + if (((mViewFlags & CLICKABLE) == CLICKABLE || + (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) && + (event.getRepeatCount() == 0)) { + setPressed(true); + if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { + postCheckForLongClick(); + } + return true; + } + break; + } + } + return result; + } + + /** + * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) + * KeyEvent.Callback.onKeyMultiple()}: perform clicking of the view + * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or + * {@link KeyEvent#KEYCODE_ENTER} is released. + * + * @param keyCode A key code that represents the button pressed, from + * {@link android.view.KeyEvent}. + * @param event The KeyEvent object that defines the button action. + */ + public boolean onKeyUp(int keyCode, KeyEvent event) { + boolean result = false; + + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: { + if ((mViewFlags & ENABLED_MASK) == DISABLED) { + return true; + } + if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) { + setPressed(false); + + if (!mHasPerformedLongPress) { + // This is a tap, so remove the longpress check + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + + result = performClick(); + } + } + break; + } + } + return result; + } + + /** + * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) + * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle + * the event). + * + * @param keyCode A key code that represents the button pressed, from + * {@link android.view.KeyEvent}. + * @param repeatCount The number of times the action was made. + * @param event The KeyEvent object that defines the button action. + */ + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return false; + } + + /** + * Called when an unhandled key shortcut event occurs. + * + * @param keyCode The value in event.getKeyCode(). + * @param event Description of the key event. + * @return If you handled the event, return true. If you want to allow the + * event to be handled by the next receiver, return false. + */ + public boolean onKeyShortcut(int keyCode, KeyEvent event) { + return false; + } + + /** + * Show the context menu for this view. It is not safe to hold on to the + * menu after returning from this method. + * + * @param menu The context menu to populate + */ + public void createContextMenu(ContextMenu menu) { + ContextMenuInfo menuInfo = getContextMenuInfo(); + + // Sets the current menu info so all items added to menu will have + // my extra info set. + ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo); + + onCreateContextMenu(menu); + if (mOnCreateContextMenuListener != null) { + mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo); + } + + // Clear the extra information so subsequent items that aren't mine don't + // have my extra info. + ((MenuBuilder)menu).setCurrentMenuInfo(null); + + if (mParent != null) { + mParent.createContextMenu(menu); + } + } + + /** + * Views should implement this if they have extra information to associate + * with the context menu. The return result is supplied as a parameter to + * the {@link OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} + * callback. + * + * @return Extra information about the item for which the context menu + * should be shown. This information will vary across different + * subclasses of View. + */ + protected ContextMenuInfo getContextMenuInfo() { + return null; + } + + /** + * Views should implement this if the view itself is going to add items to + * the context menu. + * + * @param menu the context menu to populate + */ + protected void onCreateContextMenu(ContextMenu menu) { + } + + /** + * Implement this method to handle trackball motion events. The + * <em>relative</em> movement of the trackball since the last event + * can be retrieve with {@link MotionEvent#getX MotionEvent.getX()} and + * {@link MotionEvent#getY MotionEvent.getY()}. These are normalized so + * that a movement of 1 corresponds to the user pressing one DPAD key (so + * they will often be fractional values, representing the more fine-grained + * movement information available from a trackball). + * + * @param event The motion event. + * @return True if the event was handled, false otherwise. + */ + public boolean onTrackballEvent(MotionEvent event) { + return false; + } + + /** + * Implement this method to handle touch screen motion events. + * + * @param event The motion event. + * @return True if the event was handled, false otherwise. + */ + public boolean onTouchEvent(MotionEvent event) { + final int viewFlags = mViewFlags; + + if ((viewFlags & ENABLED_MASK) == DISABLED) { + // A disabled view that is clickable still consumes the touch + // events, it just doesn't respond to them. + return (((viewFlags & CLICKABLE) == CLICKABLE || + (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); + } + + if (mTouchDelegate != null) { + if (mTouchDelegate.onTouchEvent(event)) { + return true; + } + } + + if (((viewFlags & CLICKABLE) == CLICKABLE || + (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + if ((mPrivateFlags & PRESSED) != 0) { + // take focus if we don't have it already and we should in + // touch mode. + boolean focusTaken = false; + if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { + focusTaken = requestFocus(); + } + + if (!mHasPerformedLongPress) { + // This is a tap, so remove the longpress check + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + + // Only perform take click actions if we were in the pressed state + if (!focusTaken) { + performClick(); + } + } + + final UnsetPressedState unsetPressedState = new UnsetPressedState(); + if (!post(unsetPressedState)) { + // If the post failed, unpress right now + unsetPressedState.run(); + } + } + break; + + case MotionEvent.ACTION_DOWN: + mPrivateFlags |= PRESSED; + refreshDrawableState(); + if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { + postCheckForLongClick(); + } + break; + + case MotionEvent.ACTION_CANCEL: + mPrivateFlags &= ~PRESSED; + refreshDrawableState(); + break; + + case MotionEvent.ACTION_MOVE: + final int x = (int) event.getX(); + final int y = (int) event.getY(); + + // Be lenient about moving outside of buttons + int slop = ViewConfiguration.getTouchSlop(); + if ((x < 0 - slop) || (x >= getWidth() + slop) || + (y < 0 - slop) || (y >= getHeight() + slop)) { + // Outside button + if ((mPrivateFlags & PRESSED) != 0) { + // Remove any future long press checks + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + + // Need to switch from pressed to not pressed + mPrivateFlags &= ~PRESSED; + refreshDrawableState(); + } + } else { + // Inside button + if ((mPrivateFlags & PRESSED) == 0) { + // Need to switch from not pressed to pressed + mPrivateFlags |= PRESSED; + refreshDrawableState(); + } + } + break; + } + return true; + } + + return false; + } + + /** + * Cancels a pending long press. Your subclass can use this if you + * want the context menu to come up if the user presses and holds + * at the same place, but you don't want it to come up if they press + * and then move around enough to cause scrolling. + */ + public void cancelLongPress() { + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + } + + /** + * Sets the TouchDelegate for this View. + */ + public void setTouchDelegate(TouchDelegate delegate) { + mTouchDelegate = delegate; + } + + /** + * Gets the TouchDelegate for this View. + */ + public TouchDelegate getTouchDelegate() { + return mTouchDelegate; + } + + /** + * Set flags controlling behavior of this view. + * + * @param flags Constant indicating the value which should be set + * @param mask Constant indicating the bit range that should be changed + */ + void setFlags(int flags, int mask) { + int old = mViewFlags; + mViewFlags = (mViewFlags & ~mask) | (flags & mask); + + int changed = mViewFlags ^ old; + if (changed == 0) { + return; + } + int privateFlags = mPrivateFlags; + + /* Check if the FOCUSABLE bit has changed */ + if (((changed & FOCUSABLE_MASK) != 0) && + ((privateFlags & HAS_BOUNDS) !=0)) { + if (((old & FOCUSABLE_MASK) == FOCUSABLE) + && ((privateFlags & FOCUSED) != 0)) { + /* Give up focus if we are no longer focusable */ + clearFocus(); + } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE) + && ((privateFlags & FOCUSED) == 0)) { + /* + * Tell the view system that we are now available to take focus + * if no one else already has it. + */ + if (mParent != null) mParent.focusableViewAvailable(this); + } + } + + if ((flags & VISIBILITY_MASK) == VISIBLE) { + if ((changed & VISIBILITY_MASK) != 0) { + /* + * If this view is becoming visible, set the DRAWN flag so that + * the next invalidate() will not be skipped. + */ + mPrivateFlags |= DRAWN; + + needGlobalAttributesUpdate(true); + + // a view becoming visible is worth notifying the parent + // about in case nothing has focus. even if this specific view + // isn't focusable, it may contain something that is, so let + // the root view try to give this focus if nothing else does. + if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) { + mParent.focusableViewAvailable(this); + } + } + } + + /* Check if the GONE bit has changed */ + if ((changed & GONE) != 0) { + needGlobalAttributesUpdate(false); + requestLayout(); + invalidate(); + + if (((mViewFlags & VISIBILITY_MASK) == GONE) && hasFocus()) { + clearFocus(); + } + } + + /* Check if the VISIBLE bit has changed */ + if ((changed & INVISIBLE) != 0) { + needGlobalAttributesUpdate(false); + invalidate(); + + if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) { + // root view becoming invisible shouldn't clear focus + if (getRootView() != this) { + clearFocus(); + } + } + } + + if ((changed & WILL_NOT_CACHE_DRAWING) != 0) { + if (mDrawingCache != null) { + mDrawingCache.recycle(); + } + mDrawingCache = null; + } + + if ((changed & DRAWING_CACHE_ENABLED) != 0) { + if (mDrawingCache != null) { + mDrawingCache.recycle(); + } + mDrawingCache = null; + mPrivateFlags &= ~DRAWING_CACHE_VALID; + } + + if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) { + if (mDrawingCache != null) { + mDrawingCache.recycle(); + } + mDrawingCache = null; + mPrivateFlags &= ~DRAWING_CACHE_VALID; + } + + if ((changed & DRAW_MASK) != 0) { + if ((mViewFlags & WILL_NOT_DRAW) != 0) { + if (mBGDrawable != null) { + mPrivateFlags &= ~SKIP_DRAW; + mPrivateFlags |= ONLY_DRAWS_BACKGROUND; + } else { + mPrivateFlags |= SKIP_DRAW; + } + } else { + mPrivateFlags &= ~SKIP_DRAW; + } + requestLayout(); + invalidate(); + } + + if ((changed & KEEP_SCREEN_ON) != 0) { + if (mParent != null) { + mParent.recomputeViewAttributes(this); + } + } + } + + /** + * Change the view's z order in the tree, so it's on top of other sibling + * views + */ + public void bringToFront() { + if (mParent != null) { + mParent.bringChildToFront(this); + } + } + + /** + * This is called in response to an internal scroll in this view (i.e., the + * view scrolled its own contents). This is typically as a result of + * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been + * called. + * + * @param l Current horizontal scroll origin. + * @param t Current vertical scroll origin. + * @param oldl Previous horizontal scroll origin. + * @param oldt Previous vertical scroll origin. + */ + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + mBackgroundSizeChanged = true; + } + + /** + * This is called during layout when the size of this view has changed. If + * you were just added to the view hierarchy, you're called with the old + * values of 0. + * + * @param w Current width of this view. + * @param h Current height of this view. + * @param oldw Old width of this view. + * @param oldh Old height of this view. + */ + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + } + + /** + * Called by draw to draw the child views. This may be overridden + * by derived classes to gain control just before its children are drawn + * (but after its own view has been drawn). + * @param canvas the canvas on which to draw the view + */ + protected void dispatchDraw(Canvas canvas) { + } + + /** + * Gets the parent of this view. Note that the parent is a + * ViewParent and not necessarily a View. + * + * @return Parent of this view. + */ + public final ViewParent getParent() { + return mParent; + } + + /** + * Return the scrolled left position of this view. This is the left edge of + * the displayed part of your view. You do not need to draw any pixels + * farther left, since those are outside of the frame of your view on + * screen. + * + * @return The left edge of the displayed part of your view, in pixels. + */ + public final int getScrollX() { + return mScrollX; + } + + /** + * Return the scrolled top position of this view. This is the top edge of + * the displayed part of your view. You do not need to draw any pixels above + * it, since those are outside of the frame of your view on screen. + * + * @return The top edge of the displayed part of your view, in pixels. + */ + public final int getScrollY() { + return mScrollY; + } + + /** + * Return the width of the your view. + * + * @return The width of your view, in pixels. + */ + @ViewDebug.ExportedProperty + public final int getWidth() { + return mRight - mLeft; + } + + /** + * Return the height of your view. + * + * @return The height of your view, in pixels. + */ + @ViewDebug.ExportedProperty + public final int getHeight() { + return mBottom - mTop; + } + + /** + * Return the visible drawing bounds of your view. Fills in the output + * rectangle with the values from getScrollX(), getScrollY(), + * getWidth(), and getHeight(). + * + * @param outRect The (scrolled) drawing bounds of the view. + */ + public void getDrawingRect(Rect outRect) { + outRect.left = mScrollX; + outRect.top = mScrollY; + outRect.right = mScrollX + (mRight - mLeft); + outRect.bottom = mScrollY + (mBottom - mTop); + } + + /** + * The width of this view as measured in the most recent call to measure(). + * This should be used during measurement and layout calculations only. Use + * {@link #getWidth()} to see how wide a view is after layout. + * + * @return The measured width of this view. + */ + public final int getMeasuredWidth() { + return mMeasuredWidth; + } + + /** + * The height of this view as measured in the most recent call to measure(). + * This should be used during measurement and layout calculations only. Use + * {@link #getHeight()} to see how tall a view is after layout. + * + * @return The measured height of this view. + */ + public final int getMeasuredHeight() { + return mMeasuredHeight; + } + + /** + * Top position of this view relative to its parent. + * + * @return The top of this view, in pixels. + */ + public final int getTop() { + return mTop; + } + + /** + * Bottom position of this view relative to its parent. + * + * @return The bottom of this view, in pixels. + */ + public final int getBottom() { + return mBottom; + } + + /** + * Left position of this view relative to its parent. + * + * @return The left edge of this view, in pixels. + */ + public final int getLeft() { + return mLeft; + } + + /** + * Right position of this view relative to its parent. + * + * @return The right edge of this view, in pixels. + */ + public final int getRight() { + return mRight; + } + + /** + * Hit rectangle in parent's coordinates + * + * @param outRect The hit rectangle of the view. + */ + public void getHitRect(Rect outRect) { + outRect.set(mLeft, mTop, mRight, mBottom); + } + + /** + * When a view has focus and the user navigates away from it, the next view is searched for + * starting from the rectangle filled in by this method. + * + * By default, the rectange is the {@link #getDrawingRect})of the view. However, if your + * view maintains some idea of internal selection, such as a cursor, or a selected row + * or column, you should override this method and fill in a more specific rectangle. + * + * @param r The rectangle to fill in, in this view's coordinates. + */ + public void getFocusedRect(Rect r) { + getDrawingRect(r); + } + + /** + * If some part of this view is not clipped by any of its parents, then + * return that area in r in global (root) coordinates. To convert r to local + * coordinates, offset it by -globalOffset (e.g. r.offset(-globalOffset.x, + * -globalOffset.y)) If the view is completely clipped or translated out, + * return false. + * + * @param r If true is returned, r holds the global coordinates of the + * visible portion of this view. + * @param globalOffset If true is returned, globalOffset holds the dx,dy + * between this view and its root. globalOffet may be null. + * @return true if r is non-empty (i.e. part of the view is visible at the + * root level. + */ + public boolean getGlobalVisibleRect(Rect r, Point globalOffset) { + int width = mRight - mLeft; + int height = mBottom - mTop; + if (width > 0 && height > 0) { + r.set(0, 0, width, height); + if (globalOffset != null) { + globalOffset.set(-mScrollX, -mScrollY); + } + return mParent == null || mParent.getChildVisibleRect(this, r, globalOffset); + } + return false; + } + + public final boolean getGlobalVisibleRect(Rect r) { + return getGlobalVisibleRect(r, null); + } + + public final boolean getLocalVisibleRect(Rect r) { + Point offset = new Point(); + if (getGlobalVisibleRect(r, offset)) { + r.offset(-offset.x, -offset.y); // make r local + return true; + } + return false; + } + + /** + * Offset this view's vertical location by the specified number of pixels. + * + * @param offset the number of pixels to offset the view by + */ + public void offsetTopAndBottom(int offset) { + mTop += offset; + mBottom += offset; + } + + /** + * Offset this view's horizontal location by the specified amount of pixels. + * + * @param offset the numer of pixels to offset the view by + */ + public void offsetLeftAndRight(int offset) { + mLeft += offset; + mRight += offset; + } + + /** + * Get the LayoutParams associated with this view. All views should have + * layout parameters. These supply parameters to the <i>parent</i> of this + * view specifying how it should be arranged. There are many subclasses of + * ViewGroup.LayoutParams, and these correspond to the different subclasses + * of ViewGroup that are responsible for arranging their children. + * @return The LayoutParams associated with this view + */ + @ViewDebug.ExportedProperty(deepExport = true, prefix = "layout_") + public ViewGroup.LayoutParams getLayoutParams() { + return mLayoutParams; + } + + /** + * Set the layout parameters associated with this view. These supply + * parameters to the <i>parent</i> of this view specifying how it should be + * arranged. There are many subclasses of ViewGroup.LayoutParams, and these + * correspond to the different subclasses of ViewGroup that are responsible + * for arranging their children. + * + * @param params the layout parameters for this view + */ + public void setLayoutParams(ViewGroup.LayoutParams params) { + if (params == null) { + throw new NullPointerException("params == null"); + } + mLayoutParams = params; + requestLayout(); + } + + /** + * Set the scrolled position of your view. This will cause a call to + * {@link #onScrollChanged(int, int, int, int)} and the view will be + * invalidated. + * @param x the x position to scroll to + * @param y the y position to scroll to + */ + public void scrollTo(int x, int y) { + if (mScrollX != x || mScrollY != y) { + int oldX = mScrollX; + int oldY = mScrollY; + mScrollX = x; + mScrollY = y; + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + invalidate(); + } + } + + /** + * Move the scrolled position of your view. This will cause a call to + * {@link #onScrollChanged(int, int, int, int)} and the view will be + * invalidated. + * @param x the amount of pixels to scroll by horizontally + * @param y the amount of pixels to scroll by vertically + */ + public void scrollBy(int x, int y) { + scrollTo(mScrollX + x, mScrollY + y); + } + + /** + * Mark the the area defined by dirty as needing to be drawn. If the view is + * visible, {@link #onDraw} will be called at some point in the future. + * This must be called from a UI thread. To call from a non-UI thread, call + * {@link #postInvalidate()}. + * + * WARNING: This method is destructive to dirty. + * @param dirty the rectangle representing the bounds of the dirty region + */ + public void invalidate(Rect dirty) { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE); + } + + if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) { + mPrivateFlags &= ~DRAWING_CACHE_VALID; + ViewParent p = mParent; + if (p != null) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; + mTempRect.set(dirty.left - scrollX, dirty.top - scrollY, + dirty.right - scrollX, dirty.bottom - scrollY); + p.invalidateChild(this, mTempRect); + } + } + } + + /** + * Mark the the area defined by the rect (l,t,r,b) as needing to be drawn. + * The coordinates of the dirty rect are relative to the view. + * If the view is visible, {@link #onDraw} will be called at some point + * in the future. This must be called from a UI thread. To call + * from a non-UI thread, call {@link #postInvalidate()}. + * @param l the left position of the dirty region + * @param t the top position of the dirty region + * @param r the right position of the dirty region + * @param b the bottom position of the dirty region + */ + public void invalidate(int l, int t, int r, int b) { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE); + } + + if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) { + mPrivateFlags &= ~DRAWING_CACHE_VALID; + ViewParent p = mParent; + if (p != null && l < r && t < b) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; + mTempRect.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY); + p.invalidateChild(this, mTempRect); + } + } + } + + /** + * Invalidate the whole view. If the view is visible, {@link #onDraw} will + * be called at some point in the future. This must be called from a + * UI thread. To call from a non-UI thread, call {@link #postInvalidate()}. + */ + public void invalidate() { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE); + } + + if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) { + mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID; + ViewParent p = mParent; + if (p != null) { + mTempRect.set(0, 0, mRight - mLeft, mBottom - mTop); + // Don't call invalidate -- we don't want to internally scroll + // our own bounds + p.invalidateChild(this, mTempRect); + } + } + } + + /** + * @return A handler associated with the thread running the View. This + * handler can be used to pump events in the UI events queue. + */ + protected Handler getHandler() { + if (mAttachInfo != null) { + return mAttachInfo.mHandler; + } + return null; + } + + /** + * Causes the Runnable to be added to the message queue. + * The runnable will be run on the user interface thread. + * + * @param action The Runnable that will be executed. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public boolean post(Runnable action) { + Handler handler; + if (mAttachInfo != null) { + handler = mAttachInfo.mHandler; + } else { + handler = ViewRoot.sUiThreads.get(); + if (handler == null) { + // Assume that post will succeed later + ViewRoot.sRunQueue.post(action); + return true; + } + } + + return handler.post(action); + } + + /** + * Causes the Runnable to be added to the message queue, to be run + * after the specified amount of time elapses. + * The runnable will be run on the user interface thread. + * + * @param action The Runnable that will be executed. + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + * + * @return true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- + * if the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public boolean postDelayed(Runnable action, long delayMillis) { + Handler handler; + if (mAttachInfo != null) { + handler = mAttachInfo.mHandler; + } else { + handler = ViewRoot.sUiThreads.get(); + if (handler == null) { + // Assume that post will succeed later + ViewRoot.sRunQueue.postDelayed(action, delayMillis); + return true; + } + } + + return handler.postDelayed(action, delayMillis); + } + + /** + * Removes the specified Runnable from the message queue. + * + * @param action The Runnable to remove from the message handling queue + * + * @return true if this view could ask the Handler to remove the Runnable, + * false otherwise. When the returned value is true, the Runnable + * may or may not have been actually removed from the message queue + * (for instance, if the Runnable was not in the queue already.) + */ + public boolean removeCallbacks(Runnable action) { + Handler handler; + if (mAttachInfo != null) { + handler = mAttachInfo.mHandler; + } else { + handler = ViewRoot.sUiThreads.get(); + if (handler == null) { + // Assume that post will succeed later + ViewRoot.sRunQueue.removeCallbacks(action); + return true; + } + } + + handler.removeCallbacks(action); + return true; + } + + /** + * Cause an invalidate to happen on a subsequent cycle through the event loop. + * Use this to invalidate the View from a non-UI thread. + * + * @see #invalidate() + */ + public void postInvalidate() { + // We try only with the AttachInfo because there's no point in invalidating + // if we are not attached to our window + if (mAttachInfo != null) { + Message msg = Message.obtain(); + msg.what = AttachInfo.INVALIDATE_MSG; + msg.obj = this; + mAttachInfo.mHandler.sendMessage(msg); + } + } + + /** + * Cause an invalidate of the specified area to happen on a subsequent cycle + * through the event loop. Use this to invalidate the View from a non-UI thread. + * + * @param left The left coordinate of the rectangle to invalidate. + * @param top The top coordinate of the rectangle to invalidate. + * @param right The right coordinate of the rectangle to invalidate. + * @param bottom The bottom coordinate of the rectangle to invalidate. + * + * @see #invalidate(int, int, int, int) + * @see #invalidate(Rect) + */ + public void postInvalidate(int left, int top, int right, int bottom) { + // We try only with the AttachInfo because there's no point in invalidating + // if we are not attached to our window + if (mAttachInfo != null) { + Message msg = Message.obtain(); + msg.what = AttachInfo.INVALIDATE_RECT_MSG; + msg.obj = this; + msg.arg1 = (left << 16) | (top & 0xFFFF); + msg.arg2 = (right << 16) | (bottom & 0xFFFF); + mAttachInfo.mHandler.sendMessage(msg); + } + } + + /** + * Cause an invalidate to happen on a subsequent cycle through the event + * loop. Waits for the specified amount of time. + * + * @param delayMilliseconds the duration in milliseconds to delay the + * invalidation by + */ + public void postInvalidateDelayed(long delayMilliseconds) { + // We try only with the AttachInfo because there's no point in invalidating + // if we are not attached to our window + if (mAttachInfo != null) { + Message msg = Message.obtain(); + msg.what = AttachInfo.INVALIDATE_MSG; + msg.obj = this; + mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); + } + } + + /** + * Cause an invalidate of the specified area to happen on a subsequent cycle + * through the event loop. Waits for the specified amount of time. + * + * @param delayMilliseconds the duration in milliseconds to delay the + * invalidation by + * @param left The left coordinate of the rectangle to invalidate. + * @param top The top coordinate of the rectangle to invalidate. + * @param right The right coordinate of the rectangle to invalidate. + * @param bottom The bottom coordinate of the rectangle to invalidate. + */ + public void postInvalidateDelayed(long delayMilliseconds, int left, int top + , int right, int bottom) { + // We try only with the AttachInfo because there's no point in invalidating + // if we are not attached to our window + if (mAttachInfo != null) { + Message msg = Message.obtain(); + msg.what = AttachInfo.INVALIDATE_RECT_MSG; + msg.obj = this; + msg.arg1 = (left << 16) | (top & 0xFFFF); + msg.arg2 = (right << 16) | (bottom & 0xFFFF); + mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); + } + } + + /** + * Called by a parent to request that a child update its values for mScrollX + * and mScrollY if necessary. This will typically be done if the child is + * animating a scroll using a {@link android.widget.Scroller Scroller} + * object. + */ + public void computeScroll() { + } + + /** + * <p>Indicate whether the horizontal edges are faded when the view is + * scrolled horizontally.</p> + * + * @return true if the horizontal edges should are faded on scroll, false + * otherwise + * + * @see #setHorizontalFadingEdgeEnabled(boolean) + * @attr ref android.R.styleable#View_fadingEdge + */ + public boolean isHorizontalFadingEdgeEnabled() { + return (mViewFlags & FADING_EDGE_HORIZONTAL) == FADING_EDGE_HORIZONTAL; + } + + /** + * <p>Define whether the horizontal edges should be faded when this view + * is scrolled horizontally.</p> + * + * @param horizontalFadingEdgeEnabled true if the horizontal edges should + * be faded when the view is scrolled + * horizontally + * + * @see #isHorizontalFadingEdgeEnabled() + * @attr ref android.R.styleable#View_fadingEdge + */ + public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) { + if (isHorizontalFadingEdgeEnabled() != horizontalFadingEdgeEnabled) { + if (horizontalFadingEdgeEnabled) { + initScrollCache(); + } + + mViewFlags ^= FADING_EDGE_HORIZONTAL; + } + } + + /** + * <p>Indicate whether the vertical edges are faded when the view is + * scrolled horizontally.</p> + * + * @return true if the vertical edges should are faded on scroll, false + * otherwise + * + * @see #setVerticalFadingEdgeEnabled(boolean) + * @attr ref android.R.styleable#View_fadingEdge + */ + public boolean isVerticalFadingEdgeEnabled() { + return (mViewFlags & FADING_EDGE_VERTICAL) == FADING_EDGE_VERTICAL; + } + + /** + * <p>Define whether the vertical edges should be faded when this view + * is scrolled vertically.</p> + * + * @param verticalFadingEdgeEnabled true if the vertical edges should + * be faded when the view is scrolled + * vertically + * + * @see #isVerticalFadingEdgeEnabled() + * @attr ref android.R.styleable#View_fadingEdge + */ + public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) { + if (isVerticalFadingEdgeEnabled() != verticalFadingEdgeEnabled) { + if (verticalFadingEdgeEnabled) { + initScrollCache(); + } + + mViewFlags ^= FADING_EDGE_VERTICAL; + } + } + + /** + * Returns the strength, or intensity, of the top faded edge. The strength is + * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation + * returns 0.0 or 1.0 but no value in between. + * + * Subclasses should override this method to provide a smoother fade transition + * when scrolling occurs. + * + * @return the intensity of the top fade as a float between 0.0f and 1.0f + */ + protected float getTopFadingEdgeStrength() { + return computeVerticalScrollOffset() > 0 ? 1.0f : 0.0f; + } + + /** + * Returns the strength, or intensity, of the bottom faded edge. The strength is + * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation + * returns 0.0 or 1.0 but no value in between. + * + * Subclasses should override this method to provide a smoother fade transition + * when scrolling occurs. + * + * @return the intensity of the bottom fade as a float between 0.0f and 1.0f + */ + protected float getBottomFadingEdgeStrength() { + return computeVerticalScrollOffset() + computeVerticalScrollExtent() < + computeVerticalScrollRange() ? 1.0f : 0.0f; + } + + /** + * Returns the strength, or intensity, of the left faded edge. The strength is + * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation + * returns 0.0 or 1.0 but no value in between. + * + * Subclasses should override this method to provide a smoother fade transition + * when scrolling occurs. + * + * @return the intensity of the left fade as a float between 0.0f and 1.0f + */ + protected float getLeftFadingEdgeStrength() { + return computeHorizontalScrollOffset() > 0 ? 1.0f : 0.0f; + } + + /** + * Returns the strength, or intensity, of the right faded edge. The strength is + * a value between 0.0 (no fade) and 1.0 (full fade). The default implementation + * returns 0.0 or 1.0 but no value in between. + * + * Subclasses should override this method to provide a smoother fade transition + * when scrolling occurs. + * + * @return the intensity of the right fade as a float between 0.0f and 1.0f + */ + protected float getRightFadingEdgeStrength() { + return computeHorizontalScrollOffset() + computeHorizontalScrollExtent() < + computeHorizontalScrollRange() ? 1.0f : 0.0f; + } + + /** + * <p>Indicate whether the horizontal scrollbar should be drawn or not. The + * scrollbar is not drawn by default.</p> + * + * @return true if the horizontal scrollbar should be painted, false + * otherwise + * + * @see #setHorizontalScrollBarEnabled(boolean) + */ + public boolean isHorizontalScrollBarEnabled() { + return (mViewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL; + } + + /** + * <p>Define whether the horizontal scrollbar should be drawn or not. The + * scrollbar is not drawn by default.</p> + * + * @param horizontalScrollBarEnabled true if the horizontal scrollbar should + * be painted + * + * @see #isHorizontalScrollBarEnabled() + */ + public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) { + if (isHorizontalScrollBarEnabled() != horizontalScrollBarEnabled) { + mViewFlags ^= SCROLLBARS_HORIZONTAL; + recomputePadding(); + } + } + + /** + * <p>Indicate whether the vertical scrollbar should be drawn or not. The + * scrollbar is not drawn by default.</p> + * + * @return true if the vertical scrollbar should be painted, false + * otherwise + * + * @see #setVerticalScrollBarEnabled(boolean) + */ + public boolean isVerticalScrollBarEnabled() { + return (mViewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL; + } + + /** + * <p>Define whether the vertical scrollbar should be drawn or not. The + * scrollbar is not drawn by default.</p> + * + * @param verticalScrollBarEnabled true if the vertical scrollbar should + * be painted + * + * @see #isVerticalScrollBarEnabled() + */ + public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) { + if (isVerticalScrollBarEnabled() != verticalScrollBarEnabled) { + mViewFlags ^= SCROLLBARS_VERTICAL; + recomputePadding(); + } + } + + private void recomputePadding() { + setPadding(mPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom); + } + + /** + * <p>Specify the style of the scrollbars. The scrollbars can be overlaid or + * inset. When inset, they add to the padding of the view. And the scrollbars + * can be drawn inside the padding area or on the edge of the view. For example, + * if a view has a background drawable and you want to draw the scrollbars + * inside the padding specified by the drawable, you can use + * SCROLLBARS_INSIDE_OVERLAY or SCROLLBARS_INSIDE_INSET. If you want them to + * appear at the edge of the view, ignoring the padding, then you can use + * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.</p> + * @param style the style of the scrollbars. Should be one of + * SCROLLBARS_INSIDE_OVERLAY, SCROLLBARS_INSIDE_INSET, + * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET. + * @see #SCROLLBARS_INSIDE_OVERLAY + * @see #SCROLLBARS_INSIDE_INSET + * @see #SCROLLBARS_OUTSIDE_OVERLAY + * @see #SCROLLBARS_OUTSIDE_INSET + */ + public void setScrollBarStyle(int style) { + if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) { + mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK); + recomputePadding(); + } + } + + /** + * <p>Returns the current scrollbar style.</p> + * @return the current scrollbar style + * @see #SCROLLBARS_INSIDE_OVERLAY + * @see #SCROLLBARS_INSIDE_INSET + * @see #SCROLLBARS_OUTSIDE_OVERLAY + * @see #SCROLLBARS_OUTSIDE_INSET + */ + public int getScrollBarStyle() { + return mViewFlags & SCROLLBARS_STYLE_MASK; + } + + /** + * <p>Compute the horizontal range that the horizontal scrollbar + * represents.</p> + * + * <p>The range is expressed in arbitrary units that must be the same as the + * units used by {@link #computeHorizontalScrollExtent()} and + * {@link #computeHorizontalScrollOffset()}.</p> + * + * <p>The default range is the drawing width of this view.</p> + * + * @return the total horizontal range represented by the horizontal + * scrollbar + * + * @see #computeHorizontalScrollExtent() + * @see #computeHorizontalScrollOffset() + * @see android.widget.ScrollBarDrawable + */ + protected int computeHorizontalScrollRange() { + return getWidth(); + } + + /** + * <p>Compute the horizontal offset of the horizontal scrollbar's thumb + * within the horizontal range. This value is used to compute the position + * of the thumb within the scrollbar's track.</p> + * + * <p>The range is expressed in arbitrary units that must be the same as the + * units used by {@link #computeHorizontalScrollRange()} and + * {@link #computeHorizontalScrollExtent()}.</p> + * + * <p>The default offset is the scroll offset of this view.</p> + * + * @return the horizontal offset of the scrollbar's thumb + * + * @see #computeHorizontalScrollRange() + * @see #computeHorizontalScrollExtent() + * @see android.widget.ScrollBarDrawable + */ + protected int computeHorizontalScrollOffset() { + return mScrollX; + } + + /** + * <p>Compute the horizontal extent of the horizontal scrollbar's thumb + * within the horizontal range. This value is used to compute the length + * of the thumb within the scrollbar's track.</p> + * + * <p>The range is expressed in arbitrary units that must be the same as the + * units used by {@link #computeHorizontalScrollRange()} and + * {@link #computeHorizontalScrollOffset()}.</p> + * + * <p>The default extent is the drawing width of this view.</p> + * + * @return the horizontal extent of the scrollbar's thumb + * + * @see #computeHorizontalScrollRange() + * @see #computeHorizontalScrollOffset() + * @see android.widget.ScrollBarDrawable + */ + protected int computeHorizontalScrollExtent() { + return getWidth(); + } + + /** + * <p>Compute the vertical range that the vertical scrollbar represents.</p> + * + * <p>The range is expressed in arbitrary units that must be the same as the + * units used by {@link #computeVerticalScrollExtent()} and + * {@link #computeVerticalScrollOffset()}.</p> + * + * @return the total vertical range represented by the vertical scrollbar + * + * <p>The default range is the drawing height of this view.</p> + * + * @see #computeVerticalScrollExtent() + * @see #computeVerticalScrollOffset() + * @see android.widget.ScrollBarDrawable + */ + protected int computeVerticalScrollRange() { + return getHeight(); + } + + /** + * <p>Compute the vertical offset of the vertical scrollbar's thumb + * within the horizontal range. This value is used to compute the position + * of the thumb within the scrollbar's track.</p> + * + * <p>The range is expressed in arbitrary units that must be the same as the + * units used by {@link #computeVerticalScrollRange()} and + * {@link #computeVerticalScrollExtent()}.</p> + * + * <p>The default offset is the scroll offset of this view.</p> + * + * @return the vertical offset of the scrollbar's thumb + * + * @see #computeVerticalScrollRange() + * @see #computeVerticalScrollExtent() + * @see android.widget.ScrollBarDrawable + */ + protected int computeVerticalScrollOffset() { + return mScrollY; + } + + /** + * <p>Compute the vertical extent of the horizontal scrollbar's thumb + * within the vertical range. This value is used to compute the length + * of the thumb within the scrollbar's track.</p> + * + * <p>The range is expressed in arbitrary units that must be the same as the + * units used by {@link #computeHorizontalScrollRange()} and + * {@link #computeVerticalScrollOffset()}.</p> + * + * <p>The default extent is the drawing height of this view.</p> + * + * @return the vertical extent of the scrollbar's thumb + * + * @see #computeVerticalScrollRange() + * @see #computeVerticalScrollOffset() + * @see android.widget.ScrollBarDrawable + */ + protected int computeVerticalScrollExtent() { + return getHeight(); + } + + /** + * <p>Request the drawing of the horizontal and the vertical scrollbar. The + * scrollbars are painted only if they have been awakened first.</p> + * + * @param canvas the canvas on which to draw the scrollbars + */ + private void onDrawScrollBars(Canvas canvas) { + // scrollbars are drawn only when the animation is running + final ScrollabilityCache cache = mScrollCache; + if (cache != null) { + final int viewFlags = mViewFlags; + + final boolean drawHorizontalScrollBar = + (viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL; + final boolean drawVerticalScrollBar = + (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL; + + if (drawVerticalScrollBar || drawHorizontalScrollBar) { + final int width = mRight - mLeft; + final int height = mBottom - mTop; + + final ScrollBarDrawable scrollBar = cache.scrollBar; + int size = scrollBar.getSize(false); + if (size <= 0) { + size = cache.scrollBarSize; + } + + if (drawHorizontalScrollBar) { + onDrawHorizontalScrollBar(canvas, scrollBar, width, height, size); + } + + if (drawVerticalScrollBar) { + onDrawVerticalScrollBar(canvas, scrollBar, width, height, size); + } + } + } + } + + /** + * <p>Draw the horizontal scrollbar if + * {@link #isHorizontalScrollBarEnabled()} returns true.</p> + * + * <p>The length of the scrollbar and its thumb is computed according to the + * values returned by {@link #computeHorizontalScrollRange()}, + * {@link #computeHorizontalScrollExtent()} and + * {@link #computeHorizontalScrollOffset()}. Refer to + * {@link android.widget.ScrollBarDrawable} for more information about how + * these values relate to each other.</p> + * + * @param canvas the canvas on which to draw the scrollbar + * @param scrollBar the scrollbar's drawable + * @param width the width of the drawing surface + * @param height the height of the drawing surface + * @param size the size of the scrollbar + * + * @see #isHorizontalScrollBarEnabled() + * @see #computeHorizontalScrollRange() + * @see #computeHorizontalScrollExtent() + * @see #computeHorizontalScrollOffset() + * @see android.widget.ScrollBarDrawable + */ + private void onDrawHorizontalScrollBar(Canvas canvas, ScrollBarDrawable scrollBar, int width, + int height, int size) { + + final int viewFlags = mViewFlags; + final int scrollX = mScrollX; + final int scrollY = mScrollY; + final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0; + final int top = scrollY + height - size - (mUserPaddingBottom & inside); + + final int verticalScrollBarGap = + (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL ? + getVerticalScrollbarWidth() : 0; + + scrollBar.setBounds(scrollX + (mPaddingLeft & inside) + getScrollBarPaddingLeft(), top, + scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap, top + size); + scrollBar.setParameters( + computeHorizontalScrollRange(), + computeHorizontalScrollOffset(), + computeHorizontalScrollExtent(), false); + scrollBar.draw(canvas); + } + + /** + * <p>Draw the vertical scrollbar if {@link #isVerticalScrollBarEnabled()} + * returns true.</p> + * + * <p>The length of the scrollbar and its thumb is computed according to the + * values returned by {@link #computeVerticalScrollRange()}, + * {@link #computeVerticalScrollExtent()} and + * {@link #computeVerticalScrollOffset()}. Refer to + * {@link android.widget.ScrollBarDrawable} for more information about how + * these values relate to each other.</p> + * + * @param canvas the canvas on which to draw the scrollbar + * @param scrollBar the scrollbar's drawable + * @param width the width of the drawing surface + * @param height the height of the drawing surface + * @param size the size of the scrollbar + * + * @see #isVerticalScrollBarEnabled() + * @see #computeVerticalScrollRange() + * @see #computeVerticalScrollExtent() + * @see #computeVerticalScrollOffset() + * @see android.widget.ScrollBarDrawable + */ + private void onDrawVerticalScrollBar(Canvas canvas, ScrollBarDrawable scrollBar, int width, + int height, int size) { + + final int scrollX = mScrollX; + final int scrollY = mScrollY; + final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0; + // TODO: Deal with RTL languages to position scrollbar on left + final int left = scrollX + width - size - (mUserPaddingRight & inside); + + scrollBar.setBounds(left, scrollY + (mPaddingTop & inside), + left + size, scrollY + height - (mUserPaddingBottom & inside)); + scrollBar.setParameters( + computeVerticalScrollRange(), + computeVerticalScrollOffset(), + computeVerticalScrollExtent(), true); + scrollBar.draw(canvas); + } + + /** + * Implement this to do your drawing. + * + * @param canvas the canvas on which the background will be drawn + */ + protected void onDraw(Canvas canvas) { + } + + /* + * Caller is responsible for calling requestLayout if necessary. + * (This allows addViewInLayout to not request a new layout.) + */ + void assignParent(ViewParent parent) { + if (mParent == null) { + mParent = parent; + } else if (parent == null) { + mParent = null; + } else { + throw new RuntimeException("view " + this + " being added, but" + + " it already has a parent"); + } + } + + /** + * This is called when the view is attached to a window. At this point it + * has a Surface and will start drawing. Note that this function is + * guaranteed to be called before {@link #onDraw}, however it may be called + * any time before the first onDraw -- including before or after + * {@link #onMeasure}. + * + * @see #onDetachedFromWindow() + */ + protected void onAttachedToWindow() { + if ((mPrivateFlags & REQUEST_TRANSPARENT_REGIONS) != 0) { + mParent.requestTransparentRegion(this); + } + } + + /** + * This is called when the view is detached from a window. At this point it + * no longer has a surface for drawing. + * + * @see #onAttachedToWindow() + */ + protected void onDetachedFromWindow() { + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } + } + + /** + * @return The number of times this view has been attached to a window + */ + protected int getWindowAttachCount() { + return mWindowAttachCount; + } + + /** + * Retrieve a unique token identifying the window this view is attached to. + * @return Return the window's token for use in + * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}. + */ + public IBinder getWindowToken() { + return mAttachInfo != null ? mAttachInfo.mWindowToken : null; + } + + /** + * Retrieve a unique token identifying the top-level "real" window of + * the window that this view is attached to. That is, this is like + * {@link #getWindowToken}, except if the window this view in is a panel + * window (attached to another containing window), then the token of + * the containing window is returned instead. + * + * @return Returns the associated window token, either + * {@link #getWindowToken()} or the containing window's token. + */ + public IBinder getApplicationWindowToken() { + AttachInfo ai = mAttachInfo; + if (ai != null) { + IBinder appWindowToken = ai.mPanelParentWindowToken; + if (appWindowToken == null) { + appWindowToken = ai.mWindowToken; + } + return appWindowToken; + } + return null; + } + + /** + * Retrieve private session object this view hierarchy is using to + * communicate with the window manager. + * @return the session object to communicate with the window manager + */ + /*package*/ IWindowSession getWindowSession() { + return mAttachInfo != null ? mAttachInfo.mSession : null; + } + + /** + * @param info the {@link android.view.View.AttachInfo} to associated with + * this view + */ + void dispatchAttachedToWindow(AttachInfo info, int visibility) { + //System.out.println("Attached! " + this); + mAttachInfo = info; + mWindowAttachCount++; + if (mFloatingTreeObserver != null) { + info.mTreeObserver.merge(mFloatingTreeObserver); + mFloatingTreeObserver = null; + } + performCollectViewAttributes(visibility); + onAttachedToWindow(); + int vis = info.mWindowVisibility; + if (vis != GONE) { + onWindowVisibilityChanged(vis); + } + } + + void dispatchDetachedFromWindow() { + //System.out.println("Detached! " + this); + AttachInfo info = mAttachInfo; + if (info != null) { + int vis = info.mWindowVisibility; + if (vis != GONE) { + onWindowVisibilityChanged(GONE); + } + } + + onDetachedFromWindow(); + mAttachInfo = null; + } + + /** + * Store this view hierarchy's frozen state into the given container. + * + * @param container The SparseArray in which to save the view's state. + * + * @see #restoreHierarchyState + * @see #dispatchSaveInstanceState + * @see #onSaveInstanceState + */ + public void saveHierarchyState(SparseArray<Parcelable> container) { + dispatchSaveInstanceState(container); + } + + /** + * Called by {@link #saveHierarchyState} to store the state for this view and its children. + * May be overridden to modify how freezing happens to a view's children; for example, some + * views may want to not store state for their children. + * + * @param container The SparseArray in which to save the view's state. + * + * @see #dispatchRestoreInstanceState + * @see #saveHierarchyState + * @see #onSaveInstanceState + */ + protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { + if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { + mPrivateFlags &= ~SAVE_STATE_CALLED; + Parcelable state = onSaveInstanceState(); + if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) { + throw new IllegalStateException( + "Derived class did not call super.onSaveInstanceState()"); + } + if (state != null) { + // Log.i("View", "Freezing #" + Integer.toHexString(mID) + // + ": " + state); + container.put(mID, state); + } + } + } + + /** + * Hook allowing a view to generate a representation of its internal state + * that can later be used to create a new instance with that same state. + * This state should only contain information that is not persistent or can + * not be reconstructed later. For example, you will never store your + * current position on screen because that will be computed again when a + * new instance of the view is placed in its view hierarchy. + * <p> + * Some examples of things you may store here: the current cursor position + * in a text view (but usually not the text itself since that is stored in a + * content provider or other persistent storage), the currently selected + * item in a list view. + * + * @return Returns a Parcelable object containing the view's current dynamic + * state, or null if there is nothing interesting to save. The + * default implementation returns null. + * @see #onRestoreInstanceState + * @see #saveHierarchyState + * @see #dispatchSaveInstanceState + * @see #setSaveEnabled(boolean) + */ + protected Parcelable onSaveInstanceState() { + mPrivateFlags |= SAVE_STATE_CALLED; + return BaseSavedState.EMPTY_STATE; + } + + /** + * Restore this view hierarchy's frozen state from the given container. + * + * @param container The SparseArray which holds previously frozen states. + * + * @see #saveHierarchyState + * @see #dispatchRestoreInstanceState + * @see #onRestoreInstanceState + */ + public void restoreHierarchyState(SparseArray<Parcelable> container) { + dispatchRestoreInstanceState(container); + } + + /** + * Called by {@link #restoreHierarchyState} to retrieve the state for this view and its + * children. May be overridden to modify how restoreing happens to a view's children; for + * example, some views may want to not store state for their children. + * + * @param container The SparseArray which holds previously saved state. + * + * @see #dispatchSaveInstanceState + * @see #restoreHierarchyState + * @see #onRestoreInstanceState + */ + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + if (mID != NO_ID) { + Parcelable state = container.get(mID); + if (state != null) { + // Log.i("View", "Restoreing #" + Integer.toHexString(mID) + // + ": " + state); + mPrivateFlags &= ~SAVE_STATE_CALLED; + onRestoreInstanceState(state); + if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) { + throw new IllegalStateException( + "Derived class did not call super.onRestoreInstanceState()"); + } + } + } + } + + /** + * Hook allowing a view to re-apply a representation of its internal state that had previously + * been generated by {@link #onSaveInstanceState}. This function will never be called with a + * null state. + * + * @param state The frozen state that had previously been returned by + * {@link #onSaveInstanceState}. + * + * @see #onSaveInstanceState + * @see #restoreHierarchyState + * @see #dispatchRestoreInstanceState + */ + protected void onRestoreInstanceState(Parcelable state) { + mPrivateFlags |= SAVE_STATE_CALLED; + if (state != BaseSavedState.EMPTY_STATE && state != null) { + throw new IllegalArgumentException("Wrong state class -- expecting View State"); + } + } + + /** + * <p>Return the time at which the drawing of the view hierarchy started.</p> + * + * @return the drawing start time in milliseconds + */ + public long getDrawingTime() { + return mAttachInfo != null ? mAttachInfo.mDrawingTime : 0; + } + + /** + * <p>Enables or disables the duplication of the parent's state into this view. When + * duplication is enabled, this view gets its drawable state from its parent rather + * than from its own internal properties.</p> + * + * <p>Note: in the current implementation, setting this property to true after the + * view was added to a ViewGroup might have no effect at all. This property should + * always be used from XML or set to true before adding this view to a ViewGroup.</p> + * + * <p>Note: if this view's parent addStateFromChildren property is enabled and this + * property is enabled, an exception will be thrown.</p> + * + * @param enabled True to enable duplication of the parent's drawable state, false + * to disable it. + * + * @see #getDrawableState() + * @see #isDuplicateParentStateEnabled() + */ + public void setDuplicateParentStateEnabled(boolean enabled) { + setFlags(enabled ? DUPLICATE_PARENT_STATE : 0, DUPLICATE_PARENT_STATE); + } + + /** + * <p>Indicates whether this duplicates its drawable state from its parent.</p> + * + * @return True if this view's drawable state is duplicated from the parent, + * false otherwise + * + * @see #getDrawableState() + * @see #setDuplicateParentStateEnabled(boolean) + */ + public boolean isDuplicateParentStateEnabled() { + return (mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE; + } + + /** + * <p>Enables or disables the drawing cache. When the drawing cache is enabled, the next call + * to {@link #getDrawingCache()} or {@link #buildDrawingCache()} will draw the view in a + * bitmap. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when + * the cache is enabled. To benefit from the cache, you must request the drawing cache by + * calling {@link #getDrawingCache()} and draw it on screen if the returned bitmap is not + * null.</p> + * + * @param enabled true to enable the drawing cache, false otherwise + * + * @see #isDrawingCacheEnabled() + * @see #getDrawingCache() + * @see #buildDrawingCache() + */ + public void setDrawingCacheEnabled(boolean enabled) { + setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED); + } + + /** + * <p>Indicates whether the drawing cache is enabled for this view.</p> + * + * @return true if the drawing cache is enabled + * + * @see #setDrawingCacheEnabled(boolean) + * @see #getDrawingCache() + */ + @ViewDebug.ExportedProperty + public boolean isDrawingCacheEnabled() { + return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED; + } + + /** + * <p>Returns the bitmap in which this view drawing is cached. The returned bitmap + * is null when caching is disabled. If caching is enabled and the cache is not ready, + * this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not + * draw from the cache when the cache is enabled. To benefit from the cache, you must + * request the drawing cache by calling this method and draw it on screen if the + * returned bitmap is not null.</p> + * + * @return a bitmap representing this view or null if cache is disabled + * + * @see #setDrawingCacheEnabled(boolean) + * @see #isDrawingCacheEnabled() + * @see #buildDrawingCache() + * @see #destroyDrawingCache() + */ + public Bitmap getDrawingCache() { + if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) { + return null; + } + if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED && + ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null)) { + buildDrawingCache(); + } + return mDrawingCache; + } + + /** + * <p>Frees the resources used by the drawing cache. If you call + * {@link #buildDrawingCache()} manually without calling + * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you + * should cleanup the cache with this method afterwards.</p> + * + * @see #setDrawingCacheEnabled(boolean) + * @see #buildDrawingCache() + * @see #getDrawingCache() + */ + public void destroyDrawingCache() { + if (mDrawingCache != null) { + mDrawingCache.recycle(); + mDrawingCache = null; + } + } + + /** + * Setting a solid background color for the drawing cache's bitmaps will improve + * perfromance and memory usage. Note, though that this should only be used if this + * view will always be drawn on top of a solid color. + * + * @param color The background color to use for the drawing cache's bitmap + * + * @see #setDrawingCacheEnabled(boolean) + * @see #buildDrawingCache() + * @see #getDrawingCache() + */ + public void setDrawingCacheBackgroundColor(int color) { + mDrawingCacheBackgroundColor = color; + } + + /** + * @see #setDrawingCacheBackgroundColor(int) + * + * @return The background color to used for the drawing cache's bitmap + */ + public int getDrawingCacheBackgroundColor() { + return mDrawingCacheBackgroundColor; + } + + /** + * <p>Forces the drawing cache to be built if the drawing cache is invalid.</p> + * + * <p>If you call {@link #buildDrawingCache()} manually without calling + * {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you + * should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p> + * + * @see #getDrawingCache() + * @see #destroyDrawingCache() + */ + public void buildDrawingCache() { + if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null) { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE); + } + if (ViewRoot.PROFILE_DRAWING) { + EventLog.writeEvent(60002, hashCode()); + } + + final int width = mRight - mLeft; + final int height = mBottom - mTop; + + final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor; + final boolean opaque = drawingCacheBackgroundColor != 0 || + (mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE); + + if (width <= 0 || height <= 0 || + (width * height * (opaque ? 2 : 4) >= // Projected bitmap size in bytes + ViewConfiguration.getMaximumDrawingCacheSize())) { + if (mDrawingCache != null) { + mDrawingCache.recycle(); + } + mDrawingCache = null; + return; + } + + boolean clear = true; + Bitmap bitmap = mDrawingCache; + + if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) { + + Bitmap.Config quality; + if (!opaque) { + switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) { + case DRAWING_CACHE_QUALITY_AUTO: + quality = Bitmap.Config.ARGB_8888; + break; + case DRAWING_CACHE_QUALITY_LOW: + quality = Bitmap.Config.ARGB_4444; + break; + case DRAWING_CACHE_QUALITY_HIGH: + quality = Bitmap.Config.ARGB_8888; + break; + default: + quality = Bitmap.Config.ARGB_8888; + break; + } + } else { + quality = Bitmap.Config.RGB_565; + } + + mDrawingCache = bitmap = Bitmap.createBitmap(width, height, quality); + + clear = drawingCacheBackgroundColor != 0; + } + + Canvas canvas; + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + canvas = attachInfo.mCanvas; + if (canvas == null) { + canvas = new Canvas(); + } + canvas.setBitmap(bitmap); + // Temporarily clobber the cached Canvas in case one of our children + // is also using a drawing cache. Without this, the children would + // steal the canvas by attaching their own bitmap to it and bad, bad + // thing would happen (invisible views, corrupted drawings, etc.) + attachInfo.mCanvas = null; + } else { + // This case should hopefully never or seldom happen + canvas = new Canvas(bitmap); + } + + if (clear) { + bitmap.eraseColor(drawingCacheBackgroundColor); + } + + computeScroll(); + final int restoreCount = canvas.save(); + canvas.translate(-mScrollX, -mScrollY); + + // Fast path for layouts with no backgrounds + if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + mPrivateFlags |= DRAWN; + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); + } + dispatchDraw(canvas); + } else { + draw(canvas); + } + + canvas.restoreToCount(restoreCount); + + if (attachInfo != null) { + // Restore the cached Canvas for our siblings + attachInfo.mCanvas = canvas; + } + mPrivateFlags |= DRAWING_CACHE_VALID; + } + } + + + /** + * Manually render this view (and all of its children) to the given Canvas. + * The view must have already done a full layout before this function is + * called. When implementing a view, do not override this method; instead, + * you should implement {@link #onDraw}. + * + * @param canvas The Canvas to which the View is rendered. + */ + public void draw(Canvas canvas) { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); + } + + /* + * Draw traversal performs several drawing steps which must be executed + * in the appropriate order: + * + * 1. Draw the background + * 2. If necessary, save the canvas' layers to prepare for fading + * 3. Draw view's content + * 4. Draw children + * 5. If necessary, draw the fading edges and restore layers + * 6. Draw decorations (scrollbars for instance) + */ + + // Step 1, draw the background, if needed + int saveCount; + + final Drawable background = mBGDrawable; + if (background != null) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; + + if (mBackgroundSizeChanged) { + background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); + mBackgroundSizeChanged = false; + } + + if ((scrollX | scrollY) == 0) { + background.draw(canvas); + } else { + canvas.translate(scrollX, scrollY); + background.draw(canvas); + canvas.translate(-scrollX, -scrollY); + } + } + + // skip step 2 & 5 if possible (common case) + final int viewFlags = mViewFlags; + boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; + boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; + if (!verticalEdges && !horizontalEdges) { + // Step 3, draw the content + mPrivateFlags |= DRAWN; + onDraw(canvas); + + // Step 4, draw the children + dispatchDraw(canvas); + + // Step 6, draw decorations (scrollbars) + onDrawScrollBars(canvas); + + // we're done... + return; + } + + /* + * Here we do the full fledged routine... + * (this is an uncommon case where speed matters less, + * this is why we repeat some of the tests that have been + * done above) + */ + + boolean drawTop = false; + boolean drawBottom = false; + boolean drawLeft = false; + boolean drawRight = false; + + float topFadeStrength = 0.0f; + float bottomFadeStrength = 0.0f; + float leftFadeStrength = 0.0f; + float rightFadeStrength = 0.0f; + + // Step 2, save the canvas' layers + final int paddingLeft = mPaddingLeft; + final int paddingTop = mPaddingTop; + + final int left = mScrollX + paddingLeft; + final int right = left + mRight - mLeft - mPaddingRight - paddingLeft; + final int top = mScrollY + paddingTop; + final int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop; + + final ScrollabilityCache scrollabilityCache = mScrollCache; + int length = scrollabilityCache.fadingEdgeLength; + + // clip the fade length if top and bottom fades overlap + // overlapping fades produce odd-looking artifacts + if (verticalEdges && (top + length > bottom - length)) { + length = (bottom - top) / 2; + } + + // also clip horizontal fades if necessary + if (horizontalEdges && (left + length > right - length)) { + length = (right - left) / 2; + } + + if (verticalEdges) { + topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength())); + drawTop = topFadeStrength >= 0.0f; + bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength())); + drawBottom = bottomFadeStrength >= 0.0f; + } + + if (horizontalEdges) { + leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength())); + drawLeft = leftFadeStrength >= 0.0f; + rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength())); + drawRight = rightFadeStrength >= 0.0f; + } + + saveCount = canvas.getSaveCount(); + + int solidColor = getSolidColor(); + if (solidColor == 0) { + final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; + + if (drawTop) { + canvas.saveLayer(left, top, right, top + length, null, flags); + } + + if (drawBottom) { + canvas.saveLayer(left, bottom - length, right, bottom, null, flags); + } + + if (drawLeft) { + canvas.saveLayer(left, top, left + length, bottom, null, flags); + } + + if (drawRight) { + canvas.saveLayer(right - length, top, right, bottom, null, flags); + } + } else { + scrollabilityCache.setFadeColor(solidColor); + } + + // Step 3, draw the content + mPrivateFlags |= DRAWN; + onDraw(canvas); + + // Step 4, draw the children + dispatchDraw(canvas); + + // Step 5, draw the fade effect and restore layers + final Paint p = scrollabilityCache.paint; + final Matrix matrix = scrollabilityCache.matrix; + final Shader fade = scrollabilityCache.shader; + final float fadeHeight = scrollabilityCache.fadingEdgeLength; + + if (drawTop) { + matrix.setScale(1, fadeHeight * topFadeStrength); + matrix.postTranslate(left, top); + fade.setLocalMatrix(matrix); + canvas.drawRect(left, top, right, top + length, p); + } + + if (drawBottom) { + matrix.setScale(1, fadeHeight * bottomFadeStrength); + matrix.postRotate(180); + matrix.postTranslate(left, bottom); + fade.setLocalMatrix(matrix); + canvas.drawRect(left, bottom - length, right, bottom, p); + } + + if (drawLeft) { + matrix.setScale(1, fadeHeight * leftFadeStrength); + matrix.postRotate(-90); + matrix.postTranslate(left, top); + fade.setLocalMatrix(matrix); + canvas.drawRect(left, top, left + length, bottom, p); + } + + if (drawRight) { + matrix.setScale(1, fadeHeight * rightFadeStrength); + matrix.postRotate(90); + matrix.postTranslate(right, top); + fade.setLocalMatrix(matrix); + canvas.drawRect(right - length, top, right, bottom, p); + } + + canvas.restoreToCount(saveCount); + + // Step 6, draw decorations (scrollbars) + onDrawScrollBars(canvas); + } + + /** + * Override this if your view is known to always be drawn on top of a solid color background, + * and needs to draw fading edges. Returning a non-zero color enables the view system to + * optimize the drawing of the fading edges. If you do return a non-zero color, the alpha + * should be set to 0xFF. + * + * @see #setVerticalFadingEdgeEnabled + * @see #setHorizontalFadingEdgeEnabled + * + * @return The known solid color background for this view, or 0 if the color may vary + */ + public int getSolidColor() { + return 0; + } + + /** + * Build a human readable string representation of the specified view flags. + * + * @param flags the view flags to convert to a string + * @return a String representing the supplied flags + */ + private static String printFlags(int flags) { + String output = ""; + int numFlags = 0; + if ((flags & FOCUSABLE_MASK) == FOCUSABLE) { + output += "TAKES_FOCUS"; + numFlags++; + } + + switch (flags & VISIBILITY_MASK) { + case INVISIBLE: + if (numFlags > 0) { + output += " "; + } + output += "INVISIBLE"; + // USELESS HERE numFlags++; + break; + case GONE: + if (numFlags > 0) { + output += " "; + } + output += "GONE"; + // USELESS HERE numFlags++; + break; + default: + break; + } + return output; + } + + /** + * Build a human readable string representation of the specified private + * view flags. + * + * @param privateFlags the private view flags to convert to a string + * @return a String representing the supplied flags + */ + private static String printPrivateFlags(int privateFlags) { + String output = ""; + int numFlags = 0; + + if ((privateFlags & WANTS_FOCUS) == WANTS_FOCUS) { + output += "WANTS_FOCUS"; + numFlags++; + } + + if ((privateFlags & FOCUSED) == FOCUSED) { + if (numFlags > 0) { + output += " "; + } + output += "FOCUSED"; + numFlags++; + } + + if ((privateFlags & SELECTED) == SELECTED) { + if (numFlags > 0) { + output += " "; + } + output += "SELECTED"; + numFlags++; + } + + if ((privateFlags & IS_ROOT_NAMESPACE) == IS_ROOT_NAMESPACE) { + if (numFlags > 0) { + output += " "; + } + output += "IS_ROOT_NAMESPACE"; + numFlags++; + } + + if ((privateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + if (numFlags > 0) { + output += " "; + } + output += "HAS_BOUNDS"; + numFlags++; + } + + if ((privateFlags & DRAWN) == DRAWN) { + if (numFlags > 0) { + output += " "; + } + output += "DRAWN"; + // USELESS HERE numFlags++; + } + return output; + } + + /** + * <p>Indicates whether or not this view's layout will be requested during + * the next hierarchy layout pass.</p> + * + * @return true if the layout will be forced during next layout pass + */ + public boolean isLayoutRequested() { + return (mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT; + } + + /** + * Assign a size and position to a view and all of its + * descendants + * + * <p>This is the second phase of the layout mechanism. + * (The first is measuring). In this phase, each parent calls + * layout on all of its children to position them. + * This is typically done using the child measurements + * that were stored in the measure pass(). + * + * Derived classes with children should override + * onLayout. In that method, they should + * call layout on each of their their children. + * + * @param l Left position, relative to parent + * @param t Top position, relative to parent + * @param r Right position, relative to parent + * @param b Bottom position, relative to parent + */ + public final void layout(int l, int t, int r, int b) { + boolean changed = setFrame(l, t, r, b); + if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); + } + + onLayout(changed, l, t, r, b); + mPrivateFlags &= ~LAYOUT_REQUIRED; + } + mPrivateFlags &= ~FORCE_LAYOUT; + } + + /** + * Called from layout when this view should + * assign a size and position to each of its children. + * + * Derived classes with children should override + * this method and call layout on each of + * their their children. + * @param changed This is a new size or position for this view + * @param left Left position, relative to parent + * @param top Top position, relative to parent + * @param right Right position, relative to parent + * @param bottom Bottom position, relative to parent + */ + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + } + + /** + * Assign a size and position to this view. + * + * This is called from layout. + * + * @param left Left position, relative to parent + * @param top Top position, relative to parent + * @param right Right position, relative to parent + * @param bottom Bottom position, relative to parent + * @return true if the new size and position are different than the + * previous ones + * {@hide} + */ + protected boolean setFrame(int left, int top, int right, int bottom) { + boolean changed = false; + + if (DBG) { + System.out.println(this + " View.setFrame(" + left + "," + top + "," + + right + "," + bottom + ")"); + } + + if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { + changed = true; + + // Remember our drawn bit + int drawn = mPrivateFlags & DRAWN; + + // Invalidate our old position + invalidate(); + + + int oldWidth = mRight - mLeft; + int oldHeight = mBottom - mTop; + + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + + mPrivateFlags |= HAS_BOUNDS; + + int newWidth = right - left; + int newHeight = bottom - top; + + if (newWidth != oldWidth || newHeight != oldHeight) { + onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); + } + + if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) { + // If we are visible, force the DRAWN bit to on so that + // this invalidate will go through (at least to our parent). + // This is because someone may have invalidated this view + // before this call to setFrame came in, therby clearing + // the DRAWN bit. + mPrivateFlags |= DRAWN; + invalidate(); + } + + // Reset drawn bit to original value (invalidate turns it off) + mPrivateFlags |= drawn; + + mBackgroundSizeChanged = true; + } + return changed; + } + + /** + * Finalize inflating a view from XML. This is called as the last phase + * of inflation, after all child views have been added. + * + * <p>Even if the subclass overrides onFinishInflate, they should always be + * sure to call the super method, so that we get called. + */ + protected void onFinishInflate() { + } + + /** + * Returns the resources associated with this view. + * + * @return Resources object. + */ + public Resources getResources() { + return mResources; + } + + /** + * Invalidates the specified Drawable. + * + * @param drawable the drawable to invalidate + */ + public void invalidateDrawable(Drawable drawable) { + if (verifyDrawable(drawable)) { + final Rect dirty = drawable.getBounds(); + final int scrollX = mScrollX; + final int scrollY = mScrollY; + + invalidate(dirty.left + scrollX, dirty.top + scrollY, + dirty.right + scrollX, dirty.bottom + scrollY); + } + } + + /** + * Schedules an action on a drawable to occur at a specified time. + * + * @param who the recipient of the action + * @param what the action to run on the drawable + * @param when the time at which the action must occur. Uses the + * {@link SystemClock#uptimeMillis} timebase. + */ + public void scheduleDrawable(Drawable who, Runnable what, long when) { + if (verifyDrawable(who) && what != null && mAttachInfo != null) { + mAttachInfo.mHandler.postAtTime(what, who, when); + } + } + + /** + * Cancels a scheduled action on a drawable. + * + * @param who the recipient of the action + * @param what the action to cancel + */ + public void unscheduleDrawable(Drawable who, Runnable what) { + if (verifyDrawable(who) && what != null && mAttachInfo != null) { + mAttachInfo.mHandler.removeCallbacks(what, who); + } + } + + /** + * Unschedule any events associated with the given Drawable. This can be + * used when selecting a new Drawable into a view, so that the previous + * one is completely unscheduled. + * + * @param who The Drawable to unschedule. + * + * @see #drawableStateChanged + */ + public void unscheduleDrawable(Drawable who) { + if (mAttachInfo != null) { + mAttachInfo.mHandler.removeCallbacksAndMessages(who); + } + } + + /** + * If your view subclass is displaying its own Drawable objects, it should + * override this function and return true for any Drawable it is + * displaying. This allows animations for those drawables to be + * scheduled. + * + * <p>Be sure to call through to the super class when overriding this + * function. + * + * @param who The Drawable to verify. Return true if it is one you are + * displaying, else return the result of calling through to the + * super class. + * + * @return boolean If true than the Drawable is being displayed in the + * view; else false and it is not allowed to animate. + * + * @see #unscheduleDrawable + * @see #drawableStateChanged + */ + protected boolean verifyDrawable(Drawable who) { + return who == mBGDrawable; + } + + /** + * This function is called whenever the state of the view changes in such + * a way that it impacts the state of drawables being shown. + * + * <p>Be sure to call through to the superclass when overriding this + * function. + * + * @see Drawable#setState + */ + protected void drawableStateChanged() { + Drawable d = mBGDrawable; + if (d != null && d.isStateful()) { + d.setState(getDrawableState()); + } + } + + /** + * Call this to force a view to update its drawable state. This will cause + * drawableStateChanged to be called on this view. Views that are interested + * in the new state should call getDrawableState. + * + * @see #drawableStateChanged + * @see #getDrawableState + */ + public void refreshDrawableState() { + mPrivateFlags |= DRAWABLE_STATE_DIRTY; + drawableStateChanged(); + + ViewParent parent = mParent; + if (parent != null) { + parent.childDrawableStateChanged(this); + } + } + + /** + * Return an array of resource IDs of the drawable states representing the + * current state of the view. + * + * @return The current drawable state + * + * @see Drawable#setState + * @see #drawableStateChanged + * @see #onCreateDrawableState + */ + public final int[] getDrawableState() { + if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) { + return mDrawableState; + } else { + mDrawableState = onCreateDrawableState(0); + mPrivateFlags &= ~DRAWABLE_STATE_DIRTY; + return mDrawableState; + } + } + + /** + * Generate the new {@link android.graphics.drawable.Drawable} state for + * this view. This is called by the view + * system when the cached Drawable state is determined to be invalid. To + * retrieve the current state, you should use {@link #getDrawableState}. + * + * @param extraSpace if non-zero, this is the number of extra entries you + * would like in the returned array in which you can place your own + * states. + * + * @return Returns an array holding the current {@link Drawable} state of + * the view. + * + * @see #mergeDrawableStates + */ + protected int[] onCreateDrawableState(int extraSpace) { + if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE && + mParent instanceof View) { + return ((View) mParent).onCreateDrawableState(extraSpace); + } + + int[] drawableState; + + int privateFlags = mPrivateFlags; + + boolean isPressed = (privateFlags & PRESSED) != 0; + int viewStateIndex = (isPressed ? 1 : 0); + + boolean isEnabled = (mViewFlags & ENABLED_MASK) == ENABLED; + viewStateIndex = (viewStateIndex << 1) + (isEnabled ? 1 : 0); + + boolean isFocused = isFocused(); + viewStateIndex = (viewStateIndex << 1) + (isFocused ? 1 : 0); + + boolean isSelected = (privateFlags & SELECTED) != 0; + viewStateIndex = (viewStateIndex << 1) + (isSelected ? 1 : 0); + + boolean hasWindowFocus = hasWindowFocus(); + viewStateIndex = (viewStateIndex << 1) + (hasWindowFocus ? 1 : 0); + + drawableState = VIEW_STATE_SETS[viewStateIndex]; + + //noinspection ConstantIfStatement + if (false) { + Log.i("View", "drawableStateIndex=" + viewStateIndex); + Log.i("View", toString() + " pressed=" + isPressed + + " en=" + isEnabled + " fo=" + isFocused + + " sl=" + isSelected + " wf=" + hasWindowFocus + + ": " + Arrays.toString(drawableState)); + } + + if (extraSpace == 0) { + return drawableState; + } + + final int[] fullState; + if (drawableState != null) { + fullState = new int[drawableState.length + extraSpace]; + System.arraycopy(drawableState, 0, fullState, 0, drawableState.length); + } else { + fullState = new int[extraSpace]; + } + + return fullState; + } + + /** + * Merge your own state values in <var>additionalState</var> into the base + * state values <var>baseState</var> that were returned by + * {@link #onCreateDrawableState}. + * + * @param baseState The base state values returned by + * {@link #onCreateDrawableState}, which will be modified to also hold your + * own additional state values. + * + * @param additionalState The additional state values you would like + * added to <var>baseState</var>; this array is not modified. + * + * @return As a convenience, the <var>baseState</var> array you originally + * passed into the function is returned. + * + * @see #onCreateDrawableState + */ + protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) { + final int N = baseState.length; + int i = N - 1; + while (i >= 0 && baseState[i] == 0) { + i--; + } + System.arraycopy(additionalState, 0, baseState, i + 1, additionalState.length); + return baseState; + } + + /** + * Sets the background color for this view. + * @param color the color of the background + */ + public void setBackgroundColor(int color) { + setBackgroundDrawable(new ColorDrawable(color)); + } + + /** + * Set the background to a given resource. The resource should refer to + * a Drawable object. + * @param resid The identifier of the resource. + * @attr ref android.R.styleable#View_background + */ + public void setBackgroundResource(int resid) { + if (resid != 0 && resid == mBackgroundResource) { + return; + } + + Drawable d= null; + if (resid != 0) { + d = mResources.getDrawable(resid); + } + setBackgroundDrawable(d); + + mBackgroundResource = resid; + } + + /** + * Set the background to a given Drawable, or remove the background. If the + * background has padding, this View's padding is set to the background's + * padding. However, when a background is removed, this View's padding isn't + * touched. If setting the padding is desired, please use + * {@link #setPadding(int, int, int, int)}. + * + * @param d The Drawable to use as the background, or null to remove the + * background + */ + public void setBackgroundDrawable(Drawable d) { + boolean requestLayout = false; + + mBackgroundResource = 0; + + /* + * Regardless of whether we're setting a new background or not, we want + * to clear the previous drawable. + */ + if (mBGDrawable != null) { + mBGDrawable.setCallback(null); + unscheduleDrawable(mBGDrawable); + } + + if (d != null) { + final Rect padding = mTempRect; + if (d.getPadding(padding)) { + setPadding(padding.left, padding.top, padding.right, padding.bottom); + } + + // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or + // if it has a different minimum size, we should layout again + if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() || + mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) { + requestLayout = true; + } + + d.setCallback(this); + if (d.isStateful()) { + d.setState(getDrawableState()); + } + d.setVisible(getVisibility() == VISIBLE, false); + mBGDrawable = d; + + if ((mPrivateFlags & SKIP_DRAW) != 0) { + mPrivateFlags &= ~SKIP_DRAW; + mPrivateFlags |= ONLY_DRAWS_BACKGROUND; + requestLayout = true; + } + } else { + /* Remove the background */ + mBGDrawable = null; + + if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) { + /* + * This view ONLY drew the background before and we're removing + * the background, so now it won't draw anything + * (hence we SKIP_DRAW) + */ + mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND; + mPrivateFlags |= SKIP_DRAW; + } + + /* + * When the background is set, we try to apply its padding to this + * View. When the background is removed, we don't touch this View's + * padding. This is noted in the Javadocs. Hence, we don't need to + * requestLayout(), the invalidate() below is sufficient. + */ + + // The old background's minimum size could have affected this + // View's layout, so let's requestLayout + requestLayout = true; + } + + if (requestLayout) { + requestLayout(); + } + + mBackgroundSizeChanged = true; + invalidate(); + } + + /** + * Gets the background drawable + * @return The drawable used as the background for this view, if any. + */ + public Drawable getBackground() { + return mBGDrawable; + } + + private int getScrollBarPaddingLeft() { + // TODO: Deal with RTL languages + return 0; + } + + /* + * Returns the pixels occupied by the vertical scrollbar, if not overlaid + */ + private int getScrollBarPaddingRight() { + // TODO: Deal with RTL languages + if ((mViewFlags & SCROLLBARS_VERTICAL) == 0) { + return 0; + } + return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getVerticalScrollbarWidth(); + } + + /* + * Returns the pixels occupied by the horizontal scrollbar, if not overlaid + */ + private int getScrollBarPaddingBottom() { + if ((mViewFlags & SCROLLBARS_HORIZONTAL) == 0) { + return 0; + } + return (mViewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getHorizontalScrollbarHeight(); + } + + /** + * Sets the padding. The view may add on the space required to display + * the scrollbars, depending on the style and visibility of the scrollbars. + * So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop}, + * {@link #getPaddingRight} and {@link #getPaddingBottom} may be different + * from the values set in this call. + * + * @attr ref android.R.styleable#View_padding + * @attr ref android.R.styleable#View_paddingBottom + * @attr ref android.R.styleable#View_paddingLeft + * @attr ref android.R.styleable#View_paddingRight + * @attr ref android.R.styleable#View_paddingTop + * @param left the left padding in pixels + * @param top the top padding in pixels + * @param right the right padding in pixels + * @param bottom the bottom padding in pixels + */ + public void setPadding(int left, int top, int right, int bottom) { + boolean changed = false; + + mUserPaddingRight = right; + mUserPaddingBottom = bottom; + + if (mPaddingLeft != left + getScrollBarPaddingLeft()) { + changed = true; + mPaddingLeft = left; + } + if (mPaddingTop != top) { + changed = true; + mPaddingTop = top; + } + if (mPaddingRight != right + getScrollBarPaddingRight()) { + changed = true; + mPaddingRight = right + getScrollBarPaddingRight(); + } + if (mPaddingBottom != bottom + getScrollBarPaddingBottom()) { + changed = true; + mPaddingBottom = bottom + getScrollBarPaddingBottom(); + } + + if (changed) { + requestLayout(); + } + } + + /** + * Returns the top padding of this view. + * + * @return the top padding in pixels + */ + public int getPaddingTop() { + return mPaddingTop; + } + + /** + * Returns the bottom padding of this view. If there are inset and enabled + * scrollbars, this value may include the space required to display the + * scrollbars as well. + * + * @return the bottom padding in pixels + */ + public int getPaddingBottom() { + return mPaddingBottom; + } + + /** + * Returns the left padding of this view. If there are inset and enabled + * scrollbars, this value may include the space required to display the + * scrollbars as well. + * + * @return the left padding in pixels + */ + public int getPaddingLeft() { + return mPaddingLeft; + } + + /** + * Returns the right padding of this view. If there are inset and enabled + * scrollbars, this value may include the space required to display the + * scrollbars as well. + * + * @return the right padding in pixels + */ + public int getPaddingRight() { + return mPaddingRight; + } + + /** + * Changes the selection state of this view. A view can be selected or not. + * Note that selection is not the same as focus. Views are typically + * selected in the context of an AdapterView like ListView or GridView; + * the selected view is the view that is highlighted. + * + * @param selected true if the view must be selected, false otherwise + */ + public void setSelected(boolean selected) { + if (((mPrivateFlags & SELECTED) != 0) != selected) { + mPrivateFlags = (mPrivateFlags & ~SELECTED) | (selected ? SELECTED : 0); + invalidate(); + refreshDrawableState(); + dispatchSetSelected(selected); + } + } + + /** + * Dispatch setSelected to all of this View's children. + * + * @see #setSelected(boolean) + * + * @param selected The new selected state + */ + protected void dispatchSetSelected(boolean selected) { + } + + /** + * Indicates the selection state of this view. + * + * @return true if the view is selected, false otherwise + */ + @ViewDebug.ExportedProperty + public boolean isSelected() { + return (mPrivateFlags & SELECTED) != 0; + } + + /** + * Returns the ViewTreeObserver for this view's hierarchy. The view tree + * observer can be used to get notifications when global events, like + * layout, happen. + * + * The returned ViewTreeObserver observer is not guaranteed to remain + * valid for the lifetime of this View. If the caller of this method keeps + * a long-lived reference to ViewTreeObserver, it should always check for + * the return value of {@link ViewTreeObserver#isAlive()}. + * + * @return The ViewTreeObserver for this view's hierarchy. + */ + public ViewTreeObserver getViewTreeObserver() { + if (mAttachInfo != null) { + return mAttachInfo.mTreeObserver; + } + if (mFloatingTreeObserver == null) { + mFloatingTreeObserver = new ViewTreeObserver(); + } + return mFloatingTreeObserver; + } + + /** + * <p>Finds the topmost view in the current view hierarchy.</p> + * + * @return the topmost view containing this view + */ + public View getRootView() { + View parent = this; + + while (parent.mParent != null && parent.mParent instanceof View) { + parent = (View) parent.mParent; + } + + return parent; + } + + /** + * <p>Computes the coordinates of this view on the screen. The argument + * must be an array of two integers. After the method returns, the array + * contains the x and y location in that order.</p> + * + * @param location an array of two integers in which to hold the coordinates + */ + public void getLocationOnScreen(int[] location) { + getLocationInWindow(location); + + final AttachInfo info = mAttachInfo; + location[0] += info.mWindowLeft; + location[1] += info.mWindowTop; + } + + /** + * <p>Computes the coordinates of this view in its window. The argument + * must be an array of two integers. After the method returns, the array + * contains the x and y location in that order.</p> + * + * @param location an array of two integers in which to hold the coordinates + */ + public void getLocationInWindow(int[] location) { + if (location == null || location.length < 2) { + throw new IllegalArgumentException("location must be an array of " + + "two integers"); + } + + location[0] = mLeft; + location[1] = mTop; + + if (!(mParent instanceof View)) { + return; + } + + View parent = (View) mParent; + + while (parent != null) { + location[0] += parent.mLeft - parent.mScrollX; + location[1] += parent.mTop - parent.mScrollY; + + final ViewParent viewParent = parent.mParent; + if (viewParent != null && viewParent instanceof View) { + parent = (View) viewParent; + } else { + parent = null; + } + } + } + + /** + * {@hide} + * @param id the id of the view to be found + * @return the view of the specified id, null if cannot be found + */ + protected View findViewTraversal(int id) { + if (id == mID) { + return this; + } + return null; + } + + /** + * {@hide} + * @param tag the tag of the view to be found + * @return the view of specified tag, null if cannot be found + */ + protected View findViewWithTagTraversal(Object tag) { + if (tag != null && tag.equals(mTag)) { + return this; + } + return null; + } + + /** + * Look for a child view with the given id. If this view has the given + * id, return this view. + * + * @param id The id to search for. + * @return The view that has the given id in the hierarchy or null + */ + public final View findViewById(int id) { + if (id < 0) { + return null; + } + return findViewTraversal(id); + } + + /** + * Look for a child view with the given tag. If this view has the given + * tag, return this view. + * + * @param tag The tag to search for, using "tag.equals(getTag())". + * @return The View that has the given tag in the hierarchy or null + */ + public final View findViewWithTag(Object tag) { + if (tag == null) { + return null; + } + return findViewWithTagTraversal(tag); + } + + /** + * Sets the identifier for this view. The identifier does not have to be + * unique in this view's hierarchy. The identifier should be a positive + * number. + * + * @see #NO_ID + * @see #getId + * @see #findViewById + * + * @param id a number used to identify the view + * + * @attr ref android.R.styleable#View_id + */ + public void setId(int id) { + mID = id; + } + + /** + * {@hide} + * + * @param isRoot true if the view belongs to the root namespace, false + * otherwise + */ + public void setIsRootNamespace(boolean isRoot) { + if (isRoot) { + mPrivateFlags |= IS_ROOT_NAMESPACE; + } else { + mPrivateFlags &= ~IS_ROOT_NAMESPACE; + } + } + + /** + * {@hide} + * + * @return true if the view belongs to the root namespace, false otherwise + */ + public boolean isRootNamespace() { + return (mPrivateFlags&IS_ROOT_NAMESPACE) != 0; + } + + /** + * Returns this view's identifier. + * + * @return a positive integer used to identify the view or {@link #NO_ID} + * if the view has no ID + * + * @see #setId + * @see #findViewById + * @attr ref android.R.styleable#View_id + */ + public int getId() { + return mID; + } + + /** + * Returns this view's tag. + * + * @return the Object stored in this view as a tag + */ + @ViewDebug.ExportedProperty + public Object getTag() { + return mTag; + } + + /** + * Sets the tag associated with this view. A tag can be used to mark + * a view in its hierarchy and does not have to be unique within the + * hierarchy. Tags can also be used to store data within a view without + * resorting to another data structure. + * + * @param tag an Object to tag the view with + */ + public void setTag(final Object tag) { + mTag = tag; + } + + /** + * Prints information about this view in the log output, with the tag + * {@link #VIEW_LOG_TAG}. + * + * @hide + */ + public void debug() { + debug(0); + } + + /** + * Prints information about this view in the log output, with the tag + * {@link #VIEW_LOG_TAG}. Each line in the output is preceded with an + * indentation defined by the <code>depth</code>. + * + * @param depth the indentation level + * + * @hide + */ + protected void debug(int depth) { + String output = debugIndent(depth - 1); + + output += "+ " + this; + int id = getId(); + if (id != -1) { + output += " (id=" + id + ")"; + } + Object tag = getTag(); + if (tag != null) { + output += " (tag=" + tag + ")"; + } + Log.d(VIEW_LOG_TAG, output); + + if ((mPrivateFlags & FOCUSED) != 0) { + output = debugIndent(depth) + " FOCUSED"; + Log.d(VIEW_LOG_TAG, output); + } + + output = debugIndent(depth); + output += "frame={" + mLeft + ", " + mTop + ", " + mRight + + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY + + "} "; + Log.d(VIEW_LOG_TAG, output); + + if (mPaddingLeft != 0 || mPaddingTop != 0 || mPaddingRight != 0 + || mPaddingBottom != 0) { + output = debugIndent(depth); + output += "padding={" + mPaddingLeft + ", " + mPaddingTop + + ", " + mPaddingRight + ", " + mPaddingBottom + "}"; + Log.d(VIEW_LOG_TAG, output); + } + + output = debugIndent(depth); + output += "mMeasureWidth=" + mMeasuredWidth + + " mMeasureHeight=" + mMeasuredHeight; + Log.d(VIEW_LOG_TAG, output); + + output = debugIndent(depth); + if (mLayoutParams == null) { + output += "BAD! no layout params"; + } else { + output = mLayoutParams.debug(output); + } + Log.d(VIEW_LOG_TAG, output); + + output = debugIndent(depth); + output += "flags={"; + output += View.printFlags(mViewFlags); + output += "}"; + Log.d(VIEW_LOG_TAG, output); + + output = debugIndent(depth); + output += "privateFlags={"; + output += View.printPrivateFlags(mPrivateFlags); + output += "}"; + Log.d(VIEW_LOG_TAG, output); + } + + /** + * Creates an string of whitespaces used for indentation. + * + * @param depth the indentation level + * @return a String containing (depth * 2 + 3) * 2 white spaces + * + * @hide + */ + protected static String debugIndent(int depth) { + StringBuilder spaces = new StringBuilder((depth * 2 + 3) * 2); + for (int i = 0; i < (depth * 2) + 3; i++) { + spaces.append(' ').append(' '); + } + return spaces.toString(); + } + + /** + * <p>Return the offset of the widget's text baseline from the widget's top + * boundary. If this widget does not support baseline alignment, this + * method returns -1. </p> + * + * @return the offset of the baseline within the widget's bounds or -1 + * if baseline alignment is not supported + */ + @ViewDebug.ExportedProperty + public int getBaseline() { + return -1; + } + + /** + * Call this when something has changed which has invalidated the + * layout of this view. This will schedule a layout pass of the view + * tree. + */ + public void requestLayout() { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT); + } + + mPrivateFlags |= FORCE_LAYOUT; + + if (mParent != null && !mParent.isLayoutRequested()) { + mParent.requestLayout(); + } + } + + /** + * Forces this view to be laid out during the next layout pass. + * This method does not call requestLayout() or forceLayout() + * on the parent. + */ + public void forceLayout() { + mPrivateFlags |= FORCE_LAYOUT; + } + + /** + * <p> + * This is called to find out how big a view should be. The parent + * supplies constraint information in the width and height parameters. + * </p> + * + * <p> + * The actual mesurement work of a view is performed in + * {@link #onMeasure(int, int)}, called by this method. Therefore, only + * {@link #onMeasure(int, int)} can and must be overriden by subclasses. + * </p> + * + * + * @param widthMeasureSpec Horizontal space requirements as imposed by the + * parent + * @param heightMeasureSpec Vertical space requirements as imposed by the + * parent + * + * @see #onMeasure(int, int) + */ + public final void measure(int widthMeasureSpec, int heightMeasureSpec) { + if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || + widthMeasureSpec != mOldWidthMeasureSpec || + heightMeasureSpec != mOldHeightMeasureSpec) { + + // first clears the measured dimension flag + mPrivateFlags &= ~MEASURED_DIMENSION_SET; + + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE); + } + + // measure ourselves, this should set the measured dimension flag back + onMeasure(widthMeasureSpec, heightMeasureSpec); + + // flag not set, setMeasuredDimension() was not invoked, we raise + // an exception to warn the developer + if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) { + throw new IllegalStateException("onMeasure() did not set the" + + " measured dimension by calling" + + " setMeasuredDimension()"); + } + + mPrivateFlags |= LAYOUT_REQUIRED; + } + + mOldWidthMeasureSpec = widthMeasureSpec; + mOldHeightMeasureSpec = heightMeasureSpec; + } + + /** + * <p> + * Measure the view and its content to determine the measured width and the + * measured height. This method is invoked by {@link #measure(int, int)} and + * should be overriden by subclasses to provide accurate and efficient + * measurement of their contents. + * </p> + * + * <p> + * <strong>CONTRACT:</strong> When overriding this method, you + * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the + * measured width and height of this view. Failure to do so will trigger an + * <code>IllegalStateException</code>, thrown by + * {@link #measure(int, int)}. Calling the superclass' + * {@link #onMeasure(int, int)} is a valid use. + * </p> + * + * <p> + * The base class implementation of measure defaults to the background size, + * unless a larger size is allowed by the MeasureSpec. Subclasses should + * override {@link #onMeasure(int, int)} to provide better measurements of + * their content. + * </p> + * + * <p> + * If this method is overridden, it is the subclass's responsibility to make + * sure the measured height and width are at least the view's minimum height + * and width ({@link #getSuggestedMinimumHeight()} and + * {@link #getSuggestedMinimumWidth()}). + * </p> + * + * @param widthMeasureSpec horizontal space requirements as imposed by the parent. + * The requirements are encoded with + * {@link android.view.View.MeasureSpec}. + * @param heightMeasureSpec vertical space requirements as imposed by the parent. + * The requirements are encoded with + * {@link android.view.View.MeasureSpec}. + * + * @see #getMeasuredWidth() + * @see #getMeasuredHeight() + * @see #setMeasuredDimension(int, int) + * @see #getSuggestedMinimumHeight() + * @see #getSuggestedMinimumWidth() + * @see android.view.View.MeasureSpec#getMode(int) + * @see android.view.View.MeasureSpec#getSize(int) + */ + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), + getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); + } + + /** + * <p>This mehod must be called by {@link #onMeasure(int, int)} to store the + * measured width and measured height. Failing to do so will trigger an + * exception at measurement time.</p> + * + * @param measuredWidth the measured width of this view + * @param measuredHeight the measured height of this view + */ + protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { + mMeasuredWidth = measuredWidth; + mMeasuredHeight = measuredHeight; + + mPrivateFlags |= MEASURED_DIMENSION_SET; + } + + /** + * Utility to reconcile a desired size with constraints imposed by a MeasureSpec. + * Will take the desired size, unless a different size is imposed by the constraints. + * + * @param size How big the view wants to be + * @param measureSpec Constraints imposed by the parent + * @return The size this view should be. + */ + public static int resolveSize(int size, int measureSpec) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + result = size; + break; + case MeasureSpec.AT_MOST: + result = Math.min(size, specSize); + break; + case MeasureSpec.EXACTLY: + result = specSize; + break; + } + return result; + } + + /** + * Utility to return a default size. Uses the supplied size if the + * MeasureSpec imposed no contraints. Will get larger if allowed + * by the MeasureSpec. + * + * @param size Default size for this view + * @param measureSpec Constraints imposed by the parent + * @return The size this view should be. + */ + public static int getDefaultSize(int size, int measureSpec) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + result = size; + break; + case MeasureSpec.AT_MOST: + case MeasureSpec.EXACTLY: + result = specSize; + break; + } + return result; + } + + /** + * Returns the suggested minimum height that the view should use. This + * returns the maximum of the view's minimum height + * and the background's minimum height + * ({@link android.graphics.drawable.Drawable#getMinimumHeight()}). + * <p> + * When being used in {@link #onMeasure(int, int)}, the caller should still + * ensure the returned height is within the requirements of the parent. + * + * @return The suggested minimum height of the view. + */ + protected int getSuggestedMinimumHeight() { + int suggestedMinHeight = mMinHeight; + + if (mBGDrawable != null) { + final int bgMinHeight = mBGDrawable.getMinimumHeight(); + if (suggestedMinHeight < bgMinHeight) { + suggestedMinHeight = bgMinHeight; + } + } + + return suggestedMinHeight; + } + + /** + * Returns the suggested minimum width that the view should use. This + * returns the maximum of the view's minimum width) + * and the background's minimum width + * ({@link android.graphics.drawable.Drawable#getMinimumWidth()}). + * <p> + * When being used in {@link #onMeasure(int, int)}, the caller should still + * ensure the returned width is within the requirements of the parent. + * + * @return The suggested minimum width of the view. + */ + protected int getSuggestedMinimumWidth() { + int suggestedMinWidth = mMinWidth; + + if (mBGDrawable != null) { + final int bgMinWidth = mBGDrawable.getMinimumWidth(); + if (suggestedMinWidth < bgMinWidth) { + suggestedMinWidth = bgMinWidth; + } + } + + return suggestedMinWidth; + } + + /** + * Sets the minimum height of the view. It is not guaranteed the view will + * be able to achieve this minimum height (for example, if its parent layout + * constrains it with less available height). + * + * @param minHeight The minimum height the view will try to be. + */ + public void setMinimumHeight(int minHeight) { + mMinHeight = minHeight; + } + + /** + * Sets the minimum width of the view. It is not guaranteed the view will + * be able to achieve this minimum width (for example, if its parent layout + * constrains it with less available width). + * + * @param minWidth The minimum width the view will try to be. + */ + public void setMinimumWidth(int minWidth) { + mMinWidth = minWidth; + } + + /** + * Get the animation currently associated with this view. + * + * @return The animation that is currently playing or + * scheduled to play for this view. + */ + public Animation getAnimation() { + return mCurrentAnimation; + } + + /** + * Start the specified animation now. + * + * @param animation the animation to start now + */ + public void startAnimation(Animation animation) { + animation.setStartTime(Animation.START_ON_FIRST_FRAME); + setAnimation(animation); + invalidate(); + } + + /** + * Cancels any animations for this view. + */ + public void clearAnimation() { + mCurrentAnimation = null; + } + + /** + * Sets the next animation to play for this view. + * If you want the animation to play immediately, use + * startAnimation. This method provides allows fine-grained + * control over the start time and invalidation, but you + * must make sure that 1) the animation has a start time set, and + * 2) the view will be invalidated when the animation is supposed to + * start. + * + * @param animation The next animation, or null. + */ + public void setAnimation(Animation animation) { + mCurrentAnimation = animation; + if (animation != null) { + animation.reset(); + } + } + + /** + * Invoked by a parent ViewGroup to notify the start of the animation + * currently associated with this view. If you override this method, + * always call super.onAnimationStart(); + * + * @see #setAnimation(android.view.animation.Animation) + * @see #getAnimation() + */ + protected void onAnimationStart() { + mPrivateFlags |= ANIMATION_STARTED; + } + + /** + * Invoked by a parent ViewGroup to notify the end of the animation + * currently associated with this view. If you override this method, + * always call super.onAnimationEnd(); + * + * @see #setAnimation(android.view.animation.Animation) + * @see #getAnimation() + */ + protected void onAnimationEnd() { + mPrivateFlags &= ~ANIMATION_STARTED; + } + + /** + * Invoked if there is a Transform that involves alpha. Subclass that can + * draw themselves with the specified alpha should return true, and then + * respect that alpha when their onDraw() is called. If this returns false + * then the view may be redirected to draw into an offscreen buffer to + * fulfill the request, which will look fine, but may be slower than if the + * subclass handles it internally. The default implementation returns false. + * + * @param alpha The alpha (0..255) to apply to the view's drawing + * @return true if the view can draw with the specified alpha. + */ + protected boolean onSetAlpha(int alpha) { + return false; + } + + /** + * This is used by the RootView to perform an optimization when + * the view hierarchy contains one or several SurfaceView. + * SurfaceView is always considered transparent, but its children are not, + * therefore all View objects remove themselves from the global transparent + * region (passed as a parameter to this function). + * + * @param region The transparent region for this ViewRoot (window). + * + * @return Returns true if the effective visibility of the view at this + * point is opaque, regardless of the transparent region; returns false + * if it is possible for underlying windows to be seen behind the view. + * + * {@hide} + */ + public boolean gatherTransparentRegion(Region region) { + if (region != null) { + final int pflags = mPrivateFlags; + if ((pflags & SKIP_DRAW) == 0) { + // The SKIP_DRAW flag IS NOT set, so this view draws. We need to + // remove it from the transparent region. + getLocationInWindow(mLocation); + region.op(mLocation[0], mLocation[1], + mLocation[0] + mRight - mLeft, mLocation[1] + mBottom - mTop, + Region.Op.DIFFERENCE); + } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) { + // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable + // exists, so we remove the background drawable's non-transparent + // parts from this transparent region. + applyDrawableToTransparentRegion(mBGDrawable, region); + } + } + return true; + } + + /** + * Play a sound effect for this view. + * + * The framework will play sound effects for some built in actions, such as + * clicking, but you may wish to play these effects in your widget, + * for instance, for internal navigation. + * + * The sound effect will only be played if sound effects are enabled by the user, and + * {@link #isSoundEffectsEnabled()} is true. + * + * @param soundConstant One of the constants defined in {@link SoundEffectConstants} + */ + protected void playSoundEffect(int soundConstant) { + if (mAttachInfo == null || mAttachInfo.mSoundEffectPlayer == null || !isSoundEffectsEnabled()) { + return; + } + mAttachInfo.mSoundEffectPlayer.playSoundEffect(soundConstant); + } + + /** + * Given a Drawable whose bounds have been set to draw into this view, + * update a Region being computed for {@link #gatherTransparentRegion} so + * that any non-transparent parts of the Drawable are removed from the + * given transparent region. + * + * @param dr The Drawable whose transparency is to be applied to the region. + * @param region A Region holding the current transparency information, + * where any parts of the region that are set are considered to be + * transparent. On return, this region will be modified to have the + * transparency information reduced by the corresponding parts of the + * Drawable that are not transparent. + * {@hide} + */ + public void applyDrawableToTransparentRegion(Drawable dr, Region region) { + if (DBG) { + Log.i("View", "Getting transparent region for: " + this); + } + final Region r = dr.getTransparentRegion(); + final Rect db = dr.getBounds(); + if (r != null) { + final int w = getRight()-getLeft(); + final int h = getBottom()-getTop(); + if (db.left > 0) { + //Log.i("VIEW", "Drawable left " + db.left + " > view 0"); + r.op(0, 0, db.left, h, Region.Op.UNION); + } + if (db.right < w) { + //Log.i("VIEW", "Drawable right " + db.right + " < view " + w); + r.op(db.right, 0, w, h, Region.Op.UNION); + } + if (db.top > 0) { + //Log.i("VIEW", "Drawable top " + db.top + " > view 0"); + r.op(0, 0, w, db.top, Region.Op.UNION); + } + if (db.bottom < h) { + //Log.i("VIEW", "Drawable bottom " + db.bottom + " < view " + h); + r.op(0, db.bottom, w, h, Region.Op.UNION); + } + getLocationInWindow(mLocation); + r.translate(mLocation[0], mLocation[1]); + region.op(r, Region.Op.INTERSECT); + } else { + region.op(db, Region.Op.DIFFERENCE); + } + } + + private void postCheckForLongClick() { + mHasPerformedLongPress = false; + + if (mPendingCheckForLongPress == null) { + mPendingCheckForLongPress = new CheckForLongPress(); + } + mPendingCheckForLongPress.rememberWindowAttachCount(); + postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout()); + } + + private static int[] stateSetUnion(final int[] stateSet1, + final int[] stateSet2) { + final int stateSet1Length = stateSet1.length; + final int stateSet2Length = stateSet2.length; + final int[] newSet = new int[stateSet1Length + stateSet2Length]; + int k = 0; + int i = 0; + int j = 0; + // This is a merge of the two input state sets and assumes that the + // input sets are sorted by the order imposed by ViewDrawableStates. + for (int viewState : R.styleable.ViewDrawableStates) { + if (i < stateSet1Length && stateSet1[i] == viewState) { + newSet[k++] = viewState; + i++; + } else if (j < stateSet2Length && stateSet2[j] == viewState) { + newSet[k++] = viewState; + j++; + } + if (k > 1) { + assert(newSet[k - 1] > newSet[k - 2]); + } + } + return newSet; + } + + /** + * Inflate a view from an XML resource. This convenience method wraps the {@link + * LayoutInflater} class, which provides a full range of options for view inflation. + * + * @param context The Context object for your activity or application. + * @param resource The resource ID to inflate + * @param root A view group that will be the parent. Used to properly inflate the + * layout_* parameters. + * @see LayoutInflater + */ + public static View inflate(Context context, int resource, ViewGroup root) { + LayoutInflater factory = LayoutInflater.from(context); + return factory.inflate(resource, root); + } + + /** + * A MeasureSpec encapsulates the layout requirements passed from parent to child. + * Each MeasureSpec represents a requirement for either the width or the height. + * A MeasureSpec is comprised of a size and a mode. There are three possible + * modes: + * <dl> + * <dt>UNSPECIFIED</dt> + * <dd> + * The parent has not imposed any constraint on the child. It can be whatever size + * it wants. + * </dd> + * + * <dt>EXACTLY</dt> + * <dd> + * The parent has determined an exact size for the child. The child is going to be + * given those bounds regardless of how big it wants to be. + * </dd> + * + * <dt>AT_MOST</dt> + * <dd> + * The child can be as large as it wants up to the specified size. + * </dd> + * </dl> + * + * MeasureSpecs are implemented as ints to reduce object allocation. This class + * is provided to pack and unpack the <size, mode> tuple into the int. + */ + public static class MeasureSpec { + private static final int MODE_SHIFT = 30; + private static final int MODE_MASK = 0x3 << MODE_SHIFT; + + /** + * Measure specification mode: The parent has not imposed any constraint + * on the child. It can be whatever size it wants. + */ + public static final int UNSPECIFIED = 0 << MODE_SHIFT; + + /** + * Measure specification mode: The parent has determined an exact size + * for the child. The child is going to be given those bounds regardless + * of how big it wants to be. + */ + public static final int EXACTLY = 1 << MODE_SHIFT; + + /** + * Measure specification mode: The child can be as large as it wants up + * to the specified size. + */ + public static final int AT_MOST = 2 << MODE_SHIFT; + + /** + * Creates a measure specification based on the supplied size and mode. + * + * The mode must always be one of the following: + * <ul> + * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> + * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> + * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> + * </ul> + * + * @param size the size of the measure specification + * @param mode the mode of the measure specification + * @return the measure specification based on size and mode + */ + public static int makeMeasureSpec(int size, int mode) { + return size + mode; + } + + /** + * Extracts the mode from the supplied measure specification. + * + * @param measureSpec the measure specification to extract the mode from + * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, + * {@link android.view.View.MeasureSpec#AT_MOST} or + * {@link android.view.View.MeasureSpec#EXACTLY} + */ + public static int getMode(int measureSpec) { + return (measureSpec & MODE_MASK); + } + + /** + * Extracts the size from the supplied measure specification. + * + * @param measureSpec the measure specification to extract the size from + * @return the size in pixels defined in the supplied measure specification + */ + public static int getSize(int measureSpec) { + return (measureSpec & ~MODE_MASK); + } + + /** + * Returns a String representation of the specified measure + * specification. + * + * @param measureSpec the measure specification to convert to a String + * @return a String with the following format: "MeasureSpec: MODE SIZE" + */ + public static String toString(int measureSpec) { + int mode = getMode(measureSpec); + int size = getSize(measureSpec); + + StringBuilder sb = new StringBuilder("MeasureSpec: "); + + if (mode == UNSPECIFIED) + sb.append("UNSPECIFIED "); + else if (mode == EXACTLY) + sb.append("EXACTLY "); + else if (mode == AT_MOST) + sb.append("AT_MOST "); + else + sb.append(mode).append(" "); + + sb.append(size); + return sb.toString(); + } + } + + class CheckForLongPress implements Runnable { + + private int mOriginalWindowAttachCount; + + public void run() { + if (isPressed() && (mParent != null) && hasWindowFocus() + && mOriginalWindowAttachCount == mWindowAttachCount) { + if (performLongClick()) { + mHasPerformedLongPress = true; + } + } + } + + public void rememberWindowAttachCount() { + mOriginalWindowAttachCount = mWindowAttachCount; + } + } + + /** + * Interface definition for a callback to be invoked when a key event is + * dispatched to this view. The callback will be invoked before the key + * event is given to the view. + */ + public interface OnKeyListener { + /** + * Called when a key is dispatched to a view. This allows listeners to + * get a chance to respond before the target view. + * + * @param v The view the key has been dispatched to. + * @param keyCode The code for the physical key that was pressed + * @param event The KeyEvent object containing full information about + * the event. + * @return True if the listener has consumed the event, false otherwise. + */ + boolean onKey(View v, int keyCode, KeyEvent event); + } + + /** + * Interface definition for a callback to be invoked when a touch event is + * dispatched to this view. The callback will be invoked before the touch + * event is given to the view. + */ + public interface OnTouchListener { + /** + * Called when a touch event is dispatched to a view. This allows listeners to + * get a chance to respond before the target view. + * + * @param v The view the touch event has been dispatched to. + * @param event The MotionEvent object containing full information about + * the event. + * @return True if the listener has consumed the event, false otherwise. + */ + boolean onTouch(View v, MotionEvent event); + } + + /** + * Interface definition for a callback to be invoked when a view has been clicked and held. + */ + public interface OnLongClickListener { + /** + * Called when a view has been clicked and held. + * + * @param v The view that was clicked and held. + * + * return True if the callback consumed the long click, false otherwise + */ + boolean onLongClick(View v); + } + + /** + * Interface definition for a callback to be invoked when the focus state of + * a view changed. + */ + public interface OnFocusChangeListener { + /** + * Called when the focus state of a view has changed. + * + * @param v The view whose state has changed. + * @param hasFocus The new focus state of v. + */ + void onFocusChange(View v, boolean hasFocus); + } + + /** + * Interface definition for a callback to be invoked when a view is clicked. + */ + public interface OnClickListener { + /** + * Called when a view has been clicked. + * + * @param v The view that was clicked. + */ + void onClick(View v); + } + + /** + * Interface definition for a callback to be invoked when the context menu + * for this view is being built. + */ + public interface OnCreateContextMenuListener { + /** + * Called when the context menu for this view is being built. It is not + * safe to hold onto the menu after this method returns. + * + * @param menu The context menu that is being built + * @param v The view for which the context menu is being built + * @param menuInfo Extra information about the item for which the + * context menu should be shown. This information will vary + * depending on the class of v. + */ + void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo); + } + + private final class UnsetPressedState implements Runnable { + public void run() { + setPressed(false); + } + } + + /** + * Base class for derived classes that want to save and restore their own + * state in {@link #onSaveInstanceState}. + */ + public static class BaseSavedState extends AbsSavedState { + /** + * Constructor used when reading from a parcel. Reads the state of the superclass. + * + * @param source + */ + public BaseSavedState(Parcel source) { + super(source); + } + + /** + * Constructor called by derived classes when creating their SavedState objects + * + * @param superState The state of the superclass of this view + */ + public BaseSavedState(Parcelable superState) { + super(superState); + } + + public static final Parcelable.Creator<BaseSavedState> CREATOR = + new Parcelable.Creator<BaseSavedState>() { + public BaseSavedState createFromParcel(Parcel in) { + return new BaseSavedState(in); + } + + public BaseSavedState[] newArray(int size) { + return new BaseSavedState[size]; + } + }; + } + + /** + * A set of information given to a view when it is attached to its parent + * window. + */ + static class AttachInfo { + + interface SoundEffectPlayer { + void playSoundEffect(int effectId); + } + + IBinder mWindowToken; + IBinder mPanelParentWindowToken; + Surface mSurface; + IWindowSession mSession; + SoundEffectPlayer mSoundEffectPlayer; + + /** + * Left position of this view's window + */ + int mWindowLeft; + + /** + * Top position of this view's window + */ + int mWindowTop; + + /** + * Indicates whether the view's window currently has the focus. + */ + boolean mHasWindowFocus; + + /** + * The current visibility of the window. + */ + int mWindowVisibility; + + /** + * Indicates the time at which drawing started to occur. + */ + long mDrawingTime; + + /** + * Indicates whether the view's window is currently in touch mode. + */ + boolean mInTouchMode; + + /** + * Indicates that ViewRoot should trigger a global layout change + * the next time it performs a traversal + */ + boolean mRecomputeGlobalAttributes; + + /** + * Set to true when attributes (like mKeepScreenOn) need to be + * recomputed. + */ + boolean mAttributesChanged; + + /** + * Set during a traveral if any views want to keep the screen on. + */ + boolean mKeepScreenOn; + + /** + * The view tree observer used to dispatch global events like + * layout, pre-draw, touch mode change, etc. + */ + final ViewTreeObserver mTreeObserver = new ViewTreeObserver(); + + /** + * A Canvas used by the view hierarchy to perform bitmap caching. + */ + Canvas mCanvas; + + /** + * A Handler supplied by a view's {@link android.view.ViewRoot}. This + * handler can be used to pump events in the UI events queue. + */ + final Handler mHandler; + + /** + * Identifier for messages requesting the view to be invalidated. + * Such messages should be sent to {@link #mHandler}. + */ + static final int INVALIDATE_MSG = 0x1; + + /** + * Identifier for messages requesting the view to invalidate a region. + * Such messages should be sent to {@link #mHandler}. + */ + static final int INVALIDATE_RECT_MSG = 0x2; + + AttachInfo(Handler handler) { + this(handler, null); + } + + /** + * Creates a new set of attachment information with the specified + * events handler and thread. + * + * @param handler the events handler the view must use + */ + AttachInfo(Handler handler, SoundEffectPlayer effectPlayer) { + mHandler = handler; + mSoundEffectPlayer = effectPlayer; + } + } + + /** + * <p>ScrollabilityCache holds various fields used by a View when scrolling + * is supported. This avoids keeping too many unused fields in most + * instances of View.</p> + */ + private static class ScrollabilityCache { + public int fadingEdgeLength = ViewConfiguration.getFadingEdgeLength(); + + public int scrollBarSize = ViewConfiguration.getScrollBarSize(); + public ScrollBarDrawable scrollBar; + + public final Paint paint; + public final Matrix matrix; + public Shader shader; + + private int mLastColor = 0; + + public ScrollabilityCache() { + paint = new Paint(); + matrix = new Matrix(); + // use use a height of 1, and then wack the matrix each time we + // actually use it. + shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP); + + paint.setShader(shader); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + } + + public void setFadeColor(int color) { + if (color != 0 && color != mLastColor) { + mLastColor = color; + color |= 0xFF000000; + + shader = new LinearGradient(0, 0, 0, 1, color, 0, Shader.TileMode.CLAMP); + + paint.setShader(shader); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); + } + } + } +} diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java new file mode 100644 index 0000000..38be806 --- /dev/null +++ b/core/java/android/view/ViewConfiguration.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2006 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.view; + +/** + * Contains methods to standard constants used in the UI for timeouts, sizes, and distances. + * + */ +public class ViewConfiguration { + + /** + * Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in + * pixels + */ + private static final int SCROLL_BAR_SIZE = 6; + + /** + * Defines the length of the fading edges in pixels + */ + private static final int FADING_EDGE_LENGTH = 12; + + /** + * Defines the duration in milliseconds of the pressed state in child + * components. + */ + private static final int PRESSED_STATE_DURATION = 85; + + /** + * Defines the duration in milliseconds before a press turns into + * a long press + */ + private static final int LONG_PRESS_TIMEOUT = 500; + + /** + * Defines the duration in milliseconds we will wait to see if a touch event + * is a top or a scroll. If the user does not move within this interval, it is + * considered to be a tap. + */ + private static final int TAP_TIMEOUT = 100; + + /** + * Defines the duration in milliseconds we will wait to see if a touch event + * is a jump tap. If the user does not complete the jump tap within this interval, it is + * considered to be a tap. + */ + private static final int JUMP_TAP_TIMEOUT = 500; + + /** + * Defines the duration in milliseconds we want to display zoom controls in response + * to a user panning within an application. + */ + private static final int ZOOM_CONTROLS_TIMEOUT = 3000; + + /** + * Defines the duration in milliseconds a user needs to hold down the + * appropriate button to bring up the global actions dialog (power off, + * lock screen, etc). + */ + private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 1000; + + + /** + * Inset in pixels to look for touchable content when the user touches the edge of the screen + */ + private static final int EDGE_SLOP = 12; + + /** + * Distance a touch can wander before we think the user is scrolling in pixels + */ + private static final int TOUCH_SLOP = 12; + + /** + * Distance a touch needs to be outside of a window's bounds for it to + * count as outside for purposes of dismissing the window. + */ + private static final int WINDOW_TOUCH_SLOP = 16; + + /** + * Minimum velocity to initiate a fling, as measured in pixels per second + */ + private static final int MINIMUM_FLING_VELOCITY = 50; + + /** + * The maximum size of View's drawing cache, expressed in bytes. This size + * should be at least equal to the size of the screen in ARGB888 format. + */ + private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // One HVGA screen, ARGB8888 + + /** + * The coefficient of friction applied to flings/scrolls. + */ + private static float SCROLL_FRICTION = 0.015f; + + /** + * @return The width of the horizontal scrollbar and the height of the vertical + * scrollbar in pixels + */ + public static int getScrollBarSize() { + return SCROLL_BAR_SIZE; + } + + /** + * @return Defines the length of the fading edges in pixels + */ + public static int getFadingEdgeLength() { + return FADING_EDGE_LENGTH; + } + + /** + * @return Defines the duration in milliseconds of the pressed state in child + * components. + */ + public static int getPressedStateDuration() { + return PRESSED_STATE_DURATION; + } + + /** + * @return Defines the duration in milliseconds before a press turns into + * a long press + */ + public static int getLongPressTimeout() { + return LONG_PRESS_TIMEOUT; + } + + /** + * @return Defines the duration in milliseconds we will wait to see if a touch event + * is a top or a scroll. If the user does not move within this interval, it is + * considered to be a tap. + */ + public static int getTapTimeout() { + return TAP_TIMEOUT; + } + + /** + * @return Defines the duration in milliseconds we will wait to see if a touch event + * is a jump tap. If the user does not move within this interval, it is + * considered to be a tap. + */ + public static int getJumpTapTimeout() { + return JUMP_TAP_TIMEOUT; + } + + /** + * @return Inset in pixels to look for touchable content when the user touches the edge of the + * screen + */ + public static int getEdgeSlop() { + return EDGE_SLOP; + } + + /** + * @return Distance a touch can wander before we think the user is scrolling in pixels + */ + public static int getTouchSlop() { + return TOUCH_SLOP; + } + + /** + * @return Distance a touch must be outside the bounds of a window for it + * to be counted as outside the window for purposes of dismissing that + * window. + */ + public static int getWindowTouchSlop() { + return WINDOW_TOUCH_SLOP; + } + + /** + * Minimum velocity to initiate a fling, as measured in pixels per second + */ + public static int getMinimumFlingVelocity() { + return MINIMUM_FLING_VELOCITY; + } + + /** + * The maximum drawing cache size expressed in bytes. + * + * @return the maximum size of View's drawing cache expressed in bytes + */ + public static int getMaximumDrawingCacheSize() { + return MAXIMUM_DRAWING_CACHE_SIZE; + } + + /** + * The amount of time that the zoom controls should be + * displayed on the screen expressed in milliseconds. + * + * @return the time the zoom controls should be visible expressed + * in milliseconds. + */ + public static long getZoomControlsTimeout() { + return ZOOM_CONTROLS_TIMEOUT; + } + + /** + * The amount of time a user needs to press the relevant key to bring up + * the global actions dialog. + * + * @return how long a user needs to press the relevant key to bring up + * the global actions dialog. + */ + public static long getGlobalActionKeyTimeout() { + return GLOBAL_ACTIONS_KEY_TIMEOUT; + } + + /** + * The amount of friction applied to scrolls and flings. + * + * @return A scalar dimensionless value representing the coefficient of + * friction. + */ + public static float getScrollFriction() { + return SCROLL_FRICTION; + } +} diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java new file mode 100644 index 0000000..1bf46b4 --- /dev/null +++ b/core/java/android/view/ViewDebug.java @@ -0,0 +1,927 @@ +/* + * Copyright (C) 2007 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.view; + +import android.util.Log; +import android.content.res.Resources; +import android.graphics.Bitmap; + +import java.io.File; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.io.FileOutputStream; +import java.io.DataOutputStream; +import java.io.OutputStreamWriter; +import java.io.BufferedOutputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.LinkedList; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; + +/** + * Various debugging/tracing tools related to {@link View} and the view hierarchy. + */ +public class ViewDebug { + /** + * Enables or disables view hierarchy tracing. Any invoker of + * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first + * check that this value is set to true as not to affect performance. + */ + public static final boolean TRACE_HIERARCHY = false; + + /** + * Enables or disables view recycler tracing. Any invoker of + * {@link #trace(View, android.view.ViewDebug.RecyclerTraceType, int[])} should first + * check that this value is set to true as not to affect performance. + */ + public static final boolean TRACE_RECYCLER = false; + + /** + * This annotation can be used to mark fields and methods to be dumped by + * the view server. Only non-void methods with no arguments can be annotated + * by this annotation. + */ + @Target({ ElementType.FIELD, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface ExportedProperty { + /** + * When resolveId is true, and if the annotated field/method return value + * is an int, the value is converted to an Android's resource name. + * + * @return true if the property's value must be transformed into an Android + * resource name, false otherwise + */ + boolean resolveId() default false; + + /** + * A mapping can be defined to map int values to specific strings. For + * instance, View.getVisibility() returns 0, 4 or 8. However, these values + * actually mean VISIBLE, INVISIBLE and GONE. A mapping can be used to see + * these human readable values: + * + * <pre> + * @ViewDebug.ExportedProperty(mapping = { + * @ViewDebug.IntToString(from = 0, to = "VISIBLE"), + * @ViewDebug.IntToString(from = 4, to = "INVISIBLE"), + * @ViewDebug.IntToString(from = 8, to = "GONE") + * }) + * public int getVisibility() { ... + * <pre> + * + * @return An array of int to String mappings + * + * @see android.view.ViewDebug.IntToString + */ + IntToString[] mapping() default { }; + + /** + * When deep export is turned on, this property is not dumped. Instead, the + * properties contained in this property are dumped. Each child property + * is prefixed with the name of this property. + * + * @return true if the properties of this property should be dumped + * + * @see #prefix() + */ + boolean deepExport() default false; + + /** + * The prefix to use on child properties when deep export is enabled + * + * @return a prefix as a String + * + * @see #deepExport() + */ + String prefix() default ""; + } + + /** + * Defines a mapping from an int value to a String. Such a mapping can be used + * in a @ExportedProperty to provide more meaningful values to the end user. + * + * @see android.view.ViewDebug.ExportedProperty + */ + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + public @interface IntToString { + /** + * The original int value to map to a String. + * + * @return An arbitrary int value. + */ + int from(); + + /** + * The String to use in place of the original int value. + * + * @return An arbitrary non-null String. + */ + String to(); + } + + // Maximum delay in ms after which we stop trying to capture a View's drawing + private static final int CAPTURE_TIMEOUT = 4000; + + private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE"; + private static final String REMOTE_COMMAND_DUMP = "DUMP"; + private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE"; + private static final String REMOTE_COMMAND_REQUEST_LAYOUT = "REQUEST_LAYOUT"; + + private static HashMap<Class<?>, Field[]> sFieldsForClasses; + private static HashMap<Class<?>, Method[]> sMethodsForClasses; + + /** + * Defines the type of hierarhcy trace to output to the hierarchy traces file. + */ + public enum HierarchyTraceType { + INVALIDATE, + INVALIDATE_CHILD, + INVALIDATE_CHILD_IN_PARENT, + REQUEST_LAYOUT, + ON_LAYOUT, + ON_MEASURE, + DRAW, + BUILD_CACHE + } + + private static BufferedWriter sHierarchyTraces; + private static ViewRoot sHierarhcyRoot; + private static String sHierarchyTracePrefix; + + /** + * Defines the type of recycler trace to output to the recycler traces file. + */ + public enum RecyclerTraceType { + NEW_VIEW, + BIND_VIEW, + RECYCLE_FROM_ACTIVE_HEAP, + RECYCLE_FROM_SCRAP_HEAP, + MOVE_TO_ACTIVE_HEAP, + MOVE_TO_SCRAP_HEAP, + MOVE_FROM_ACTIVE_TO_SCRAP_HEAP + } + + private static class RecyclerTrace { + public int view; + public RecyclerTraceType type; + public int position; + public int indexOnScreen; + } + + private static View sRecyclerOwnerView; + private static List<View> sRecyclerViews; + private static List<RecyclerTrace> sRecyclerTraces; + private static String sRecyclerTracePrefix; + + /** + * Returns the number of instanciated Views. + * + * @return The number of Views instanciated in the current process. + * + * @hide + */ + public static long getViewInstanceCount() { + return View.sInstanceCount; + } + + /** + * Returns the number of instanciated ViewRoots. + * + * @return The number of ViewRoots instanciated in the current process. + * + * @hide + */ + public static long getViewRootInstanceCount() { + return ViewRoot.getInstanceCount(); + } + + /** + * Outputs a trace to the currently opened recycler traces. The trace records the type of + * recycler action performed on the supplied view as well as a number of parameters. + * + * @param view the view to trace + * @param type the type of the trace + * @param parameters parameters depending on the type of the trace + */ + public static void trace(View view, RecyclerTraceType type, int... parameters) { + if (sRecyclerOwnerView == null || sRecyclerViews == null) { + return; + } + + if (!sRecyclerViews.contains(view)) { + sRecyclerViews.add(view); + } + + final int index = sRecyclerViews.indexOf(view); + + RecyclerTrace trace = new RecyclerTrace(); + trace.view = index; + trace.type = type; + trace.position = parameters[0]; + trace.indexOnScreen = parameters[1]; + + sRecyclerTraces.add(trace); + } + + /** + * Starts tracing the view recycler of the specified view. The trace is identified by a prefix, + * used to build the traces files names: <code>/tmp/view-recycler/PREFIX.traces</code> and + * <code>/tmp/view-recycler/PREFIX.recycler</code>. + * + * Only one view recycler can be traced at the same time. After calling this method, any + * other invocation will result in a <code>IllegalStateException</code> unless + * {@link #stopRecyclerTracing()} is invoked before. + * + * Traces files are created only after {@link #stopRecyclerTracing()} is invoked. + * + * This method will return immediately if TRACE_RECYCLER is false. + * + * @param prefix the traces files name prefix + * @param view the view whose recycler must be traced + * + * @see #stopRecyclerTracing() + * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[]) + */ + public static void startRecyclerTracing(String prefix, View view) { + //noinspection PointlessBooleanExpression,ConstantConditions + if (!TRACE_RECYCLER) { + return; + } + + if (sRecyclerOwnerView != null) { + throw new IllegalStateException("You must call stopRecyclerTracing() before running" + + " a new trace!"); + } + + sRecyclerTracePrefix = prefix; + sRecyclerOwnerView = view; + sRecyclerViews = new ArrayList<View>(); + sRecyclerTraces = new LinkedList<RecyclerTrace>(); + } + + /** + * Stops the current view recycer tracing. + * + * Calling this method creates the file <code>/tmp/view-recycler/PREFIX.traces</code> + * containing all the traces (or method calls) relative to the specified view's recycler. + * + * Calling this method creates the file <code>/tmp/view-recycler/PREFIX.recycler</code> + * containing all of the views used by the recycler of the view supplied to + * {@link #startRecyclerTracing(String, View)}. + * + * This method will return immediately if TRACE_RECYCLER is false. + * + * @see #startRecyclerTracing(String, View) + * @see #trace(View, android.view.ViewDebug.RecyclerTraceType, int[]) + */ + public static void stopRecyclerTracing() { + //noinspection PointlessBooleanExpression,ConstantConditions + if (!TRACE_RECYCLER) { + return; + } + + if (sRecyclerOwnerView == null || sRecyclerViews == null) { + throw new IllegalStateException("You must call startRecyclerTracing() before" + + " stopRecyclerTracing()!"); + } + + File recyclerDump = new File("/tmp/view-recycler/"); + recyclerDump.mkdirs(); + + recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler"); + try { + final BufferedWriter out = new BufferedWriter(new FileWriter(recyclerDump), 8 * 1024); + + for (View view : sRecyclerViews) { + final String name = view.getClass().getName(); + out.write(name); + out.newLine(); + } + + out.close(); + } catch (IOException e) { + Log.e("View", "Could not dump recycler content"); + return; + } + + recyclerDump = new File("/tmp/view-recycler/"); + recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces"); + try { + final FileOutputStream file = new FileOutputStream(recyclerDump); + final DataOutputStream out = new DataOutputStream(file); + + for (RecyclerTrace trace : sRecyclerTraces) { + out.writeInt(trace.view); + out.writeInt(trace.type.ordinal()); + out.writeInt(trace.position); + out.writeInt(trace.indexOnScreen); + out.flush(); + } + + out.close(); + } catch (IOException e) { + Log.e("View", "Could not dump recycler traces"); + return; + } + + sRecyclerViews.clear(); + sRecyclerViews = null; + + sRecyclerTraces.clear(); + sRecyclerTraces = null; + + sRecyclerOwnerView = null; + } + + /** + * Outputs a trace to the currently opened traces file. The trace contains the class name + * and instance's hashcode of the specified view as well as the supplied trace type. + * + * @param view the view to trace + * @param type the type of the trace + */ + public static void trace(View view, HierarchyTraceType type) { + if (sHierarchyTraces == null) { + return; + } + + try { + sHierarchyTraces.write(type.name()); + sHierarchyTraces.write(' '); + sHierarchyTraces.write(view.getClass().getName()); + sHierarchyTraces.write('@'); + sHierarchyTraces.write(Integer.toHexString(view.hashCode())); + sHierarchyTraces.newLine(); + } catch (IOException e) { + Log.w("View", "Error while dumping trace of type " + type + " for view " + view); + } + } + + /** + * Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix, + * used to build the traces files names: <code>/tmp/view-hierarchy/PREFIX.traces</code> and + * <code>/tmp/view-hierarchy/PREFIX.tree</code>. + * + * Only one view hierarchy can be traced at the same time. After calling this method, any + * other invocation will result in a <code>IllegalStateException</code> unless + * {@link #stopHierarchyTracing()} is invoked before. + * + * Calling this method creates the file <code>/tmp/view-hierarchy/PREFIX.traces</code> + * containing all the traces (or method calls) relative to the specified view's hierarchy. + * + * This method will return immediately if TRACE_HIERARCHY is false. + * + * @param prefix the traces files name prefix + * @param view the view whose hierarchy must be traced + * + * @see #stopHierarchyTracing() + * @see #trace(View, android.view.ViewDebug.HierarchyTraceType) + */ + public static void startHierarchyTracing(String prefix, View view) { + //noinspection PointlessBooleanExpression,ConstantConditions + if (!TRACE_HIERARCHY) { + return; + } + + if (sHierarhcyRoot != null) { + throw new IllegalStateException("You must call stopHierarchyTracing() before running" + + " a new trace!"); + } + + File hierarchyDump = new File("/tmp/view-hierarchy/"); + hierarchyDump.mkdirs(); + + hierarchyDump = new File(hierarchyDump, prefix + ".traces"); + sHierarchyTracePrefix = prefix; + + try { + sHierarchyTraces = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024); + } catch (IOException e) { + Log.e("View", "Could not dump view hierarchy"); + return; + } + + sHierarhcyRoot = (ViewRoot) view.getRootView().getParent(); + } + + /** + * Stops the current view hierarchy tracing. This method closes the file + * <code>/tmp/view-hierarchy/PREFIX.traces</code>. + * + * Calling this method creates the file <code>/tmp/view-hierarchy/PREFIX.tree</code> containing + * the view hierarchy of the view supplied to {@link #startHierarchyTracing(String, View)}. + * + * This method will return immediately if TRACE_HIERARCHY is false. + * + * @see #startHierarchyTracing(String, View) + * @see #trace(View, android.view.ViewDebug.HierarchyTraceType) + */ + public static void stopHierarchyTracing() { + //noinspection PointlessBooleanExpression,ConstantConditions + if (!TRACE_HIERARCHY) { + return; + } + + if (sHierarhcyRoot == null || sHierarchyTraces == null) { + throw new IllegalStateException("You must call startHierarchyTracing() before" + + " stopHierarchyTracing()!"); + } + + try { + sHierarchyTraces.close(); + } catch (IOException e) { + Log.e("View", "Could not write view traces"); + } + sHierarchyTraces = null; + + File hierarchyDump = new File("/tmp/view-hierarchy/"); + hierarchyDump.mkdirs(); + hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree"); + + BufferedWriter out; + try { + out = new BufferedWriter(new FileWriter(hierarchyDump), 8 * 1024); + } catch (IOException e) { + Log.e("View", "Could not dump view hierarchy"); + return; + } + + View view = sHierarhcyRoot.getView(); + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + dumpViewHierarchy(group, out, 0); + try { + out.close(); + } catch (IOException e) { + Log.e("View", "Could not dump view hierarchy"); + } + } + + sHierarhcyRoot = null; + } + + static void dispatchCommand(View view, String command, String parameters, + OutputStream clientStream) throws IOException { + + // Paranoid but safe... + view = view.getRootView(); + + if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) { + dump(view, clientStream); + } else { + final String[] params = parameters.split(" "); + if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) { + capture(view, clientStream, params[0]); + } else if (REMOTE_COMMAND_INVALIDATE.equalsIgnoreCase(command)) { + invalidate(view, params[0]); + } else if (REMOTE_COMMAND_REQUEST_LAYOUT.equalsIgnoreCase(command)) { + requestLayout(view, params[0]); + } + } + } + + private static View findViewByHashCode(View root, String parameter) { + final String[] ids = parameter.split("@"); + final String className = ids[0]; + final int hashCode = Integer.parseInt(ids[1], 16); + + View view = root.getRootView(); + if (view instanceof ViewGroup) { + return findView((ViewGroup) view, className, hashCode); + } + + return null; + } + + private static void invalidate(View root, String parameter) { + final View view = findViewByHashCode(root, parameter); + if (view != null) { + view.postInvalidate(); + } + } + + private static void requestLayout(View root, String parameter) { + final View view = findViewByHashCode(root, parameter); + if (view != null) { + root.post(new Runnable() { + public void run() { + view.requestLayout(); + } + }); + } + } + + private static void capture(View root, final OutputStream clientStream, String parameter) + throws IOException { + + final CountDownLatch latch = new CountDownLatch(1); + final View captureView = findViewByHashCode(root, parameter); + + if (captureView != null) { + final Bitmap[] cache = new Bitmap[1]; + + final boolean hasCache = captureView.isDrawingCacheEnabled(); + final boolean willNotCache = captureView.willNotCacheDrawing(); + + if (willNotCache) { + captureView.setWillNotCacheDrawing(false); + } + + root.post(new Runnable() { + public void run() { + try { + if (!hasCache) { + captureView.buildDrawingCache(); + } + + cache[0] = captureView.getDrawingCache(); + } finally { + latch.countDown(); + } + } + }); + + try { + latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS); + + if (cache[0] != null) { + BufferedOutputStream out = null; + try { + out = new BufferedOutputStream(clientStream, 32 * 1024); + cache[0].compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + } finally { + if (out != null) { + out.close(); + } + } + } + } catch (InterruptedException e) { + Log.w("View", "Could not complete the capture of the view " + captureView); + } finally { + if (willNotCache) { + captureView.setWillNotCacheDrawing(true); + } + if (!hasCache) { + captureView.destroyDrawingCache(); + } + } + } + } + + private static void dump(View root, OutputStream clientStream) throws IOException { + BufferedWriter out = null; + try { + out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024); + View view = root.getRootView(); + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + dumpViewHierarchyWithProperties(group, out, 0); + } + out.write("DONE."); + out.newLine(); + } finally { + if (out != null) { + out.close(); + } + } + } + + private static View findView(ViewGroup group, String className, int hashCode) { + if (isRequestedView(group, className, hashCode)) { + return group; + } + + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + final View view = group.getChildAt(i); + if (view instanceof ViewGroup) { + final View found = findView((ViewGroup) view, className, hashCode); + if (found != null) { + return found; + } + } else if (isRequestedView(view, className, hashCode)) { + return view; + } + } + + return null; + } + + private static boolean isRequestedView(View view, String className, int hashCode) { + return view.getClass().getName().equals(className) && view.hashCode() == hashCode; + } + + private static void dumpViewHierarchyWithProperties(ViewGroup group, + BufferedWriter out, int level) { + if (!dumpViewWithProperties(group, out, level)) { + return; + } + + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + final View view = group.getChildAt(i); + if (view instanceof ViewGroup) { + dumpViewHierarchyWithProperties((ViewGroup) view, out, level + 1); + } else { + dumpViewWithProperties(view, out, level + 1); + } + } + } + + private static boolean dumpViewWithProperties(View view, BufferedWriter out, int level) { + try { + for (int i = 0; i < level; i++) { + out.write(' '); + } + out.write(view.getClass().getName()); + out.write('@'); + out.write(Integer.toHexString(view.hashCode())); + out.write(' '); + dumpViewProperties(view, out); + out.newLine(); + } catch (IOException e) { + Log.w("View", "Error while dumping hierarchy tree"); + return false; + } + return true; + } + + private static Field[] getExportedPropertyFields(Class<?> klass) { + if (sFieldsForClasses == null) { + sFieldsForClasses = new HashMap<Class<?>, Field[]>(); + } + final HashMap<Class<?>, Field[]> map = sFieldsForClasses; + + Field[] fields = map.get(klass); + if (fields != null) { + return fields; + } + + final ArrayList<Field> foundFields = new ArrayList<Field>(); + fields = klass.getDeclaredFields(); + + int count = fields.length; + for (int i = 0; i < count; i++) { + final Field field = fields[i]; + if (field.isAnnotationPresent(ExportedProperty.class)) { + field.setAccessible(true); + foundFields.add(field); + } + } + + fields = foundFields.toArray(new Field[foundFields.size()]); + map.put(klass, fields); + + return fields; + } + + private static Method[] getExportedPropertyMethods(Class<?> klass) { + if (sMethodsForClasses == null) { + sMethodsForClasses = new HashMap<Class<?>, Method[]>(); + } + final HashMap<Class<?>, Method[]> map = sMethodsForClasses; + + Method[] methods = map.get(klass); + if (methods != null) { + return methods; + } + + final ArrayList<Method> foundMethods = new ArrayList<Method>(); + methods = klass.getDeclaredMethods(); + + int count = methods.length; + for (int i = 0; i < count; i++) { + final Method method = methods[i]; + if (method.getParameterTypes().length == 0 && + method.isAnnotationPresent(ExportedProperty.class) && + method.getReturnType() != Void.class) { + method.setAccessible(true); + foundMethods.add(method); + } + } + + methods = foundMethods.toArray(new Method[foundMethods.size()]); + map.put(klass, methods); + + return methods; + } + + private static void dumpViewProperties(Object view, BufferedWriter out) throws IOException { + dumpViewProperties(view, out, ""); + } + + private static void dumpViewProperties(Object view, BufferedWriter out, String prefix) + throws IOException { + Class<?> klass = view.getClass(); + + do { + exportFields(view, out, klass, prefix); + exportMethods(view, out, klass, prefix); + klass = klass.getSuperclass(); + } while (klass != Object.class); + } + + private static void exportMethods(Object view, BufferedWriter out, Class<?> klass, + String prefix) throws IOException { + + final Method[] methods = getExportedPropertyMethods(klass); + + int count = methods.length; + for (int i = 0; i < count; i++) { + final Method method = methods[i]; + //noinspection EmptyCatchBlock + try { + // TODO: This should happen on the UI thread + Object methodValue = method.invoke(view, (Object[]) null); + final Class<?> returnType = method.getReturnType(); + + if (returnType == int.class) { + ExportedProperty property = method.getAnnotation(ExportedProperty.class); + if (property.resolveId() && view instanceof View) { + final Resources resources = ((View) view).getContext().getResources(); + final int id = (Integer) methodValue; + if (id >= 0) { + methodValue = resources.getResourceTypeName(id) + '/' + + resources.getResourceEntryName(id); + } else { + methodValue = "NO_ID"; + } + } else { + final IntToString[] mapping = property.mapping(); + if (mapping.length > 0) { + final int intValue = (Integer) methodValue; + boolean mapped = false; + int mappingCount = mapping.length; + for (int j = 0; j < mappingCount; j++) { + final IntToString mapper = mapping[j]; + if (mapper.from() == intValue) { + methodValue = mapper.to(); + mapped = true; + break; + } + } + + if (!mapped) { + methodValue = intValue; + } + } + } + } else if (!returnType.isPrimitive()) { + ExportedProperty property = method.getAnnotation(ExportedProperty.class); + if (property.deepExport()) { + dumpViewProperties(methodValue, out, prefix + property.prefix()); + continue; + } + } + + out.write(prefix); + out.write(method.getName()); + out.write("()="); + + if (methodValue != null) { + final String value = methodValue.toString().replace("\n", "\\n"); + out.write(String.valueOf(value.length())); + out.write(","); + out.write(value); + } else { + out.write("4,null"); + } + + out.write(' '); + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + } + } + + private static void exportFields(Object view, BufferedWriter out, Class<?> klass, String prefix) + throws IOException { + final Field[] fields = getExportedPropertyFields(klass); + + int count = fields.length; + for (int i = 0; i < count; i++) { + final Field field = fields[i]; + + //noinspection EmptyCatchBlock + try { + Object fieldValue = null; + final Class<?> type = field.getType(); + + if (type == int.class) { + ExportedProperty property = field.getAnnotation(ExportedProperty.class); + if (property.resolveId() && view instanceof View) { + final Resources resources = ((View) view).getContext().getResources(); + final int id = field.getInt(view); + if (id >= 0) { + fieldValue = resources.getResourceTypeName(id) + '/' + + resources.getResourceEntryName(id); + } else { + fieldValue = "NO_ID"; + } + } else { + final IntToString[] mapping = property.mapping(); + if (mapping.length > 0) { + final int intValue = field.getInt(view); + int mappingCount = mapping.length; + for (int j = 0; j < mappingCount; j++) { + final IntToString mapped = mapping[j]; + if (mapped.from() == intValue) { + fieldValue = mapped.to(); + break; + } + } + + if (fieldValue == null) { + fieldValue = intValue; + } + } + } + } else if (!type.isPrimitive()) { + ExportedProperty property = field.getAnnotation(ExportedProperty.class); + if (property.deepExport()) { + dumpViewProperties(field.get(view), out, prefix + property.prefix()); + continue; + } + } + + if (fieldValue == null) { + fieldValue = field.get(view); + } + + out.write(prefix); + out.write(field.getName()); + out.write('='); + + if (fieldValue != null) { + final String value = fieldValue.toString().replace("\n", "\\n"); + out.write(String.valueOf(value.length())); + out.write(","); + out.write(value); + } else { + out.write("4,null"); + } + + out.write(' '); + } catch (IllegalAccessException e) { + } + } + } + + private static void dumpViewHierarchy(ViewGroup group, BufferedWriter out, int level) { + if (!dumpView(group, out, level)) { + return; + } + + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + final View view = group.getChildAt(i); + if (view instanceof ViewGroup) { + dumpViewHierarchy((ViewGroup) view, out, level + 1); + } else { + dumpView(view, out, level + 1); + } + } + } + + private static boolean dumpView(Object view, BufferedWriter out, int level) { + try { + for (int i = 0; i < level; i++) { + out.write(' '); + } + out.write(view.getClass().getName()); + out.write('@'); + out.write(Integer.toHexString(view.hashCode())); + out.newLine(); + } catch (IOException e) { + Log.w("View", "Error while dumping hierarchy tree"); + return false; + } + return true; + } +} diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java new file mode 100644 index 0000000..9063821 --- /dev/null +++ b/core/java/android/view/ViewGroup.java @@ -0,0 +1,3389 @@ +/* + * Copyright (C) 2006 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.view; + +import com.android.internal.R; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.EventLog; +import android.util.Log; +import android.util.SparseArray; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.LayoutAnimationController; +import android.view.animation.Transformation; + +import java.util.ArrayList; + +/** + * <p> + * A <code>ViewGroup</code> is a special view that can contain other views + * (called children.) The view group is the base class for layouts and views + * containers. This class also defines the + * {@link android.view.ViewGroup.LayoutParams} class which serves as the base + * class for layouts parameters. + * </p> + * + * <p> + * Also see {@link LayoutParams} for layout attributes. + * </p> + */ +public abstract class ViewGroup extends View implements ViewParent, ViewManager { + private static final boolean DBG = false; + + /** + * Views which have been hidden or removed which need to be animated on + * their way out. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected ArrayList<View> mDisappearingChildren; + + /** + * Listener used to propagate events indicating when children are added + * and/or removed from a view group. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnHierarchyChangeListener mOnHierarchyChangeListener; + + // The view contained within this ViewGroup that has or contains focus. + private View mFocused; + + // The current transformation to apply on the child being drawn + private final Transformation mChildTransformation = new Transformation(); + + // Target of Motion events + private View mMotionTarget; + private final Rect mTempRect = new Rect(); + + // Layout animation + private LayoutAnimationController mLayoutAnimationController; + private Animation.AnimationListener mAnimationListener; + + /** + * Internal flags. + * + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected int mGroupFlags; + + // When set, ViewGroup invalidates only the child's rectangle + // Set by default + private static final int FLAG_CLIP_CHILDREN = 0x1; + + // When set, ViewGroup excludes the padding area from the invalidate rectangle + // Set by default + private static final int FLAG_CLIP_TO_PADDING = 0x2; + + // When set, dispatchDraw() will invoke invalidate(); this is set by drawChild() when + // a child needs to be invalidated and FLAG_OPTIMIZE_INVALIDATE is set + private static final int FLAG_INVALIDATE_REQUIRED = 0x4; + + // When set, dispatchDraw() will run the layout animation and unset the flag + private static final int FLAG_RUN_ANIMATION = 0x8; + + // When set, there is either no layout animation on the ViewGroup or the layout + // animation is over + // Set by default + private static final int FLAG_ANIMATION_DONE = 0x10; + + // If set, this ViewGroup has padding; if unset there is no padding and we don't need + // to clip it, even if FLAG_CLIP_TO_PADDING is set + private static final int FLAG_PADDING_NOT_NULL = 0x20; + + // When set, this ViewGroup caches its children in a Bitmap before starting a layout animation + // Set by default + private static final int FLAG_ANIMATION_CACHE = 0x40; + + // When set, this ViewGroup converts calls to invalidate(Rect) to invalidate() during a + // layout animation; this avoid clobbering the hierarchy + // Automatically set when the layout animation starts, depending on the animation's + // characteristics + private static final int FLAG_OPTIMIZE_INVALIDATE = 0x80; + + // When set, the next call to drawChild() will clear mChildTransformation's matrix + private static final int FLAG_CLEAR_TRANSFORMATION = 0x100; + + // When set, this ViewGroup invokes mAnimationListener.onAnimationEnd() and removes + // the children's Bitmap caches if necessary + // This flag is set when the layout animation is over (after FLAG_ANIMATION_DONE is set) + private static final int FLAG_NOTIFY_ANIMATION_LISTENER = 0x200; + + /** + * When set, the drawing method will call {@link #getChildDrawingOrder(int, int)} + * to get the index of the child to draw for that iteration. + */ + protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400; + + /** + * When set, this ViewGroup supports static transformations on children; this causes + * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be + * invoked when a child is drawn. + * + * Any subclass overriding + * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should + * set this flags in {@link #mGroupFlags}. + * + * This flag needs to be removed until we can add a setter for it. People + * can't be directly stuffing values in to mGroupFlags!!! + * + * {@hide} + */ + protected static final int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800; + + // When the previous drawChild() invocation used an alpha value that was lower than + // 1.0 and set it in mCachePaint + private static final int FLAG_ALPHA_LOWER_THAN_ONE = 0x1000; + + /** + * When set, this ViewGroup's drawable states also include those + * of its children. + */ + private static final int FLAG_ADD_STATES_FROM_CHILDREN = 0x2000; + + /** + * When set, this ViewGroup tries to always draw its children using their drawing cache. + */ + private static final int FLAG_ALWAYS_DRAWN_WITH_CACHE = 0x4000; + + /** + * When set, and if FLAG_ALWAYS_DRAWN_WITH_CACHE is not set, this ViewGroup will try to + * draw its children with their drawing cache. + */ + private static final int FLAG_CHILDREN_DRAWN_WITH_CACHE = 0x8000; + + /** + * When set, this group will go through its list of children to notify them of + * any drawable state change. + */ + private static final int FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE = 0x10000; + + private static final int FLAG_MASK_FOCUSABILITY = 0x60000; + + /** + * This view will get focus before any of its descendants. + */ + public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000; + + /** + * This view will get focus only if none of its descendants want it. + */ + public static final int FOCUS_AFTER_DESCENDANTS = 0x40000; + + /** + * This view will block any of its descendants from getting focus, even + * if they are focusable. + */ + public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000; + + /** + * Used to map between enum in attrubutes and flag values. + */ + private static final int[] DESCENDANT_FOCUSABILITY_FLAGS = + {FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, + FOCUS_BLOCK_DESCENDANTS}; + + /** + * When set, this ViewGroup should not intercept touch events. + */ + private static final int FLAG_DISALLOW_INTERCEPT = 0x80000; + + /** + * Indicates which types of drawing caches are to be kept in memory. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected int mPersistentDrawingCache; + + /** + * Used to indicate that no drawing cache should be kept in memory. + */ + public static final int PERSISTENT_NO_CACHE = 0x0; + + /** + * Used to indicate that the animation drawing cache should be kept in memory. + */ + public static final int PERSISTENT_ANIMATION_CACHE = 0x1; + + /** + * Used to indicate that the scrolling drawing cache should be kept in memory. + */ + public static final int PERSISTENT_SCROLLING_CACHE = 0x2; + + /** + * Used to indicate that all drawing caches should be kept in memory. + */ + public static final int PERSISTENT_ALL_CACHES = 0x3; + + /** + * We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL + * are set at the same time. + */ + protected static final int CLIP_TO_PADDING_MASK = FLAG_CLIP_TO_PADDING | FLAG_PADDING_NOT_NULL; + + // Index of the child's left position in the mLocation array + private static final int CHILD_LEFT_INDEX = 0; + // Index of the child's top position in the mLocation array + private static final int CHILD_TOP_INDEX = 1; + + // Child views of this ViewGroup + private View[] mChildren; + // Number of valid children in the mChildren array, the rest should be null or not + // considered as children + private int mChildrenCount; + + private static final int ARRAY_INITIAL_CAPACITY = 12; + private static final int ARRAY_CAPACITY_INCREMENT = 12; + + // Used to draw cached views + private final Paint mCachePaint = new Paint(); + + public ViewGroup(Context context) { + super(context); + initViewGroup(); + } + + public ViewGroup(Context context, AttributeSet attrs) { + super(context, attrs); + initViewGroup(); + initFromAttributes(context, attrs); + } + + public ViewGroup(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initViewGroup(); + initFromAttributes(context, attrs); + } + + private void initViewGroup() { + // ViewGroup doesn't draw by default + setFlags(WILL_NOT_DRAW, DRAW_MASK); + mGroupFlags |= FLAG_CLIP_CHILDREN; + mGroupFlags |= FLAG_CLIP_TO_PADDING; + mGroupFlags |= FLAG_ANIMATION_DONE; + mGroupFlags |= FLAG_ANIMATION_CACHE; + mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE; + + setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS); + + mChildren = new View[ARRAY_INITIAL_CAPACITY]; + mChildrenCount = 0; + + mCachePaint.setDither(false); + + mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE; + } + + private void initFromAttributes(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.ViewGroup); + + final int N = a.getIndexCount(); + for (int i = 0; i < N; i++) { + int attr = a.getIndex(i); + switch (attr) { + case R.styleable.ViewGroup_clipChildren: + setClipChildren(a.getBoolean(attr, true)); + break; + case R.styleable.ViewGroup_clipToPadding: + setClipToPadding(a.getBoolean(attr, true)); + break; + case R.styleable.ViewGroup_animationCache: + setAnimationCacheEnabled(a.getBoolean(attr, true)); + break; + case R.styleable.ViewGroup_persistentDrawingCache: + setPersistentDrawingCache(a.getInt(attr, PERSISTENT_SCROLLING_CACHE)); + break; + case R.styleable.ViewGroup_addStatesFromChildren: + setAddStatesFromChildren(a.getBoolean(attr, false)); + break; + case R.styleable.ViewGroup_alwaysDrawnWithCache: + setAlwaysDrawnWithCacheEnabled(a.getBoolean(attr, true)); + break; + case R.styleable.ViewGroup_layoutAnimation: + int id = a.getResourceId(attr, -1); + if (id > 0) { + setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id)); + } + break; + case R.styleable.ViewGroup_descendantFocusability: + setDescendantFocusability(DESCENDANT_FOCUSABILITY_FLAGS[a.getInt(attr, 0)]); + break; + } + } + + a.recycle(); + } + + /** + * Gets the descendant focusability of this view group. The descendant + * focusability defines the relationship between this view group and its + * descendants when looking for a view to take focus in + * {@link #requestFocus(int, android.graphics.Rect)}. + * + * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS}, + * {@link #FOCUS_BLOCK_DESCENDANTS}. + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"), + @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"), + @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS") + }) + public int getDescendantFocusability() { + return mGroupFlags & FLAG_MASK_FOCUSABILITY; + } + + /** + * Set the descendant focusability of this view group. This defines the relationship + * between this view group and its descendants when looking for a view to + * take focus in {@link #requestFocus(int, android.graphics.Rect)}. + * + * @param focusability one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS}, + * {@link #FOCUS_BLOCK_DESCENDANTS}. + */ + public void setDescendantFocusability(int focusability) { + switch (focusability) { + case FOCUS_BEFORE_DESCENDANTS: + case FOCUS_AFTER_DESCENDANTS: + case FOCUS_BLOCK_DESCENDANTS: + break; + default: + throw new IllegalArgumentException("must be one of FOCUS_BEFORE_DESCENDANTS, " + + "FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS"); + } + mGroupFlags &= ~FLAG_MASK_FOCUSABILITY; + mGroupFlags |= (focusability & FLAG_MASK_FOCUSABILITY); + } + + /** + * {@inheritDoc} + */ + @Override + void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) { + if (mFocused != null) { + mFocused.unFocus(); + mFocused = null; + } + super.handleFocusGainInternal(direction, previouslyFocusedRect); + } + + /** + * {@inheritDoc} + */ + public void requestChildFocus(View child, View focused) { + if (DBG) { + System.out.println(this + " requestChildFocus()"); + } + if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) { + return; + } + + // Unfocus us, if necessary + super.unFocus(); + + // We had a previous notion of who had focus. Clear it. + if (mFocused != child) { + if (mFocused != null) { + mFocused.unFocus(); + } + + mFocused = child; + } + if (mParent != null) { + mParent.requestChildFocus(this, focused); + } + } + + /** + * {@inheritDoc} + */ + public void focusableViewAvailable(View v) { + if (mParent != null + // shortcut: don't report a new focusable view if we block our descendants from + // getting focus + && (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS) + // shortcut: don't report a new focusable view if we already are focused + // (and we don't prefer our descendants) + // + // note: knowing that mFocused is non-null is not a good enough reason + // to break the traversal since in that case we'd actually have to find + // the focused view and make sure it wasn't FOCUS_AFTER_DESCENDANTS and + // an ancestor of v; this will get checked for at ViewRoot + && !(isFocused() && getDescendantFocusability() != FOCUS_AFTER_DESCENDANTS)) { + mParent.focusableViewAvailable(v); + } + } + + /** + * {@inheritDoc} + */ + public boolean showContextMenuForChild(View originalView) { + return mParent != null && mParent.showContextMenuForChild(originalView); + } + + /** + * Find the nearest view in the specified direction that wants to take + * focus. + * + * @param focused The view that currently has focus + * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and + * FOCUS_RIGHT, or 0 for not applicable. + */ + public View focusSearch(View focused, int direction) { + if (isRootNamespace()) { + // root namespace means we should consider ourselves the top of the + // tree for focus searching; otherwise we could be focus searching + // into other tabs. see LocalActivityManager and TabHost for more info + return FocusFinder.getInstance().findNextFocus(this, focused, direction); + } else if (mParent != null) { + return mParent.focusSearch(focused, direction); + } + return null; + } + + /** + * Called when a child of this group wants a particular rectangle to be + * positioned onto the screen. {@link ViewGroup}s overriding this can trust + * that: + * <ul> + * <li>child will be a direct child of this group</li> + * <li>rectangle will be in the child's coordinates</li> + * </ul> + * + * <p>{@link ViewGroup}s overriding this should uphold the contract:</p> + * <ul> + * <li>nothing will change if the rectangle is already visible</li> + * <li>the view port will be scrolled only just enough to make the + * rectangle visible</li> + * <ul> + * + * @param child The direct child making the request. + * @param rectangle The rectangle in the child's coordinates the child + * wishes to be on the screen. + * @param immediate True to forbid animated or delayed scrolling, + * false otherwise + * @return Whether the group scrolled to handle the operation + */ + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean dispatchUnhandledMove(View focused, int direction) { + return mFocused != null && + mFocused.dispatchUnhandledMove(focused, direction); + } + + /** + * {@inheritDoc} + */ + public void clearChildFocus(View child) { + if (DBG) { + System.out.println(this + " clearChildFocus()"); + } + + mFocused = null; + if (mParent != null) { + mParent.clearChildFocus(this); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void clearFocus() { + super.clearFocus(); + + // clear any child focus if it exists + if (mFocused != null) { + mFocused.clearFocus(); + } + } + + /** + * {@inheritDoc} + */ + @Override + void unFocus() { + if (DBG) { + System.out.println(this + " unFocus()"); + } + + super.unFocus(); + if (mFocused != null) { + mFocused.unFocus(); + } + mFocused = null; + } + + /** + * Returns the focused child of this view, if any. The child may have focus + * or contain focus. + * + * @return the focused child or null. + */ + public View getFocusedChild() { + return mFocused; + } + + /** + * Returns true if this view has or contains focus + * + * @return true if this view has or contains focus + */ + @Override + public boolean hasFocus() { + return (mPrivateFlags & FOCUSED) != 0 || mFocused != null; + } + + /* + * (non-Javadoc) + * + * @see android.view.View#findFocus() + */ + @Override + public View findFocus() { + if (DBG) { + System.out.println("Find focus in " + this + ": flags=" + + isFocused() + ", child=" + mFocused); + } + + if (isFocused()) { + return this; + } + + if (mFocused != null) { + return mFocused.findFocus(); + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasFocusable() { + if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) { + return false; + } + + if (isFocusable()) { + return true; + } + + final int descendantFocusability = getDescendantFocusability(); + if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { + final int count = mChildrenCount; + final View[] children = mChildren; + + for (int i = 0; i < count; i++) { + final View child = children[i]; + if (child.hasFocusable()) { + return true; + } + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public void addFocusables(ArrayList<View> views, int direction) { + final int focusableCount = views.size(); + + final int descendantFocusability = getDescendantFocusability(); + + if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { + final int count = mChildrenCount; + final View[] children = mChildren; + + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + child.addFocusables(views, direction); + } + } + } + + // we add ourselves (if focusable) in all cases except for when we are + // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is + // to avoid the focus search finding layouts when a more precise search + // among the focusable children would be more interesting. + if ( + descendantFocusability != FOCUS_AFTER_DESCENDANTS || + // No focusable descendants + (focusableCount == views.size())) { + super.addFocusables(views, direction); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispatchWindowFocusChanged(boolean hasFocus) { + super.dispatchWindowFocusChanged(hasFocus); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchWindowFocusChanged(hasFocus); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void addTouchables(ArrayList<View> views) { + super.addTouchables(views); + + final int count = mChildrenCount; + final View[] children = mChildren; + + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + child.addTouchables(views); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispatchWindowVisibilityChanged(int visibility) { + super.dispatchWindowVisibilityChanged(visibility); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchWindowVisibilityChanged(visibility); + } + } + + /** + * {@inheritDoc} + */ + public void recomputeViewAttributes(View child) { + ViewParent parent = mParent; + if (parent != null) parent.recomputeViewAttributes(this); + } + + @Override + void dispatchCollectViewAttributes(int visibility) { + visibility |= mViewFlags&VISIBILITY_MASK; + super.dispatchCollectViewAttributes(visibility); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchCollectViewAttributes(visibility); + } + } + + /** + * {@inheritDoc} + */ + public void bringChildToFront(View child) { + int index = indexOfChild(child); + if (index >= 0) { + removeFromArray(index); + addInArray(child, mChildrenCount); + child.mParent = this; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + return super.dispatchKeyEvent(event); + } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + return mFocused.dispatchKeyEvent(event); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + return super.dispatchTrackballEvent(event); + } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + return mFocused.dispatchTrackballEvent(event); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + final float xf = ev.getX(); + final float yf = ev.getY(); + final float scrolledXFloat = xf + mScrollX; + final float scrolledYFloat = yf + mScrollY; + final Rect frame = mTempRect; + + boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; + + if (action == MotionEvent.ACTION_DOWN) { + if (mMotionTarget != null) { + // this is weird, we got a pen down, but we thought it was + // already down! + // XXX: We should probably send an ACTION_UP to the current + // target. + mMotionTarget = null; + } + // If we're disallowing intercept or if we're allowing and we didn't + // intercept + if (disallowIntercept || !onInterceptTouchEvent(ev)) { + // reset this event's action (just to protect ourselves) + ev.setAction(MotionEvent.ACTION_DOWN); + // We know we want to dispatch the event down, find a child + // who can handle it, start with the front-most child. + final int scrolledXInt = (int) scrolledXFloat; + final int scrolledYInt = (int) scrolledYFloat; + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = count - 1; i >= 0; i--) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE + || child.getAnimation() != null) { + child.getHitRect(frame); + if (frame.contains(scrolledXInt, scrolledYInt)) { + // offset the event to the view's coordinate system + final float xc = scrolledXFloat - child.mLeft; + final float yc = scrolledYFloat - child.mTop; + ev.setLocation(xc, yc); + if (child.dispatchTouchEvent(ev)) { + // Event handled, we have a target now. + mMotionTarget = child; + return true; + } + // The event didn't get handled, try the next view. + // Don't reset the event's location, it's not + // necessary here. + } + } + } + } + } + + boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || + (action == MotionEvent.ACTION_CANCEL); + + if (isUpOrCancel) { + // Note, we've already copied the previous state to our local + // variable, so this takes effect on the next event + mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; + } + + // The event wasn't an ACTION_DOWN, dispatch it to our target if + // we have one. + final View target = mMotionTarget; + if (target == null) { + // We don't have a target, this means we're handling the + // event as a regular view. + ev.setLocation(xf, yf); + return super.dispatchTouchEvent(ev); + } + + // if have a target, see if we're allowed to and want to intercept its + // events + if (!disallowIntercept && onInterceptTouchEvent(ev)) { + final float xc = scrolledXFloat - (float) target.mLeft; + final float yc = scrolledYFloat - (float) target.mTop; + ev.setAction(MotionEvent.ACTION_CANCEL); + ev.setLocation(xc, yc); + if (!target.dispatchTouchEvent(ev)) { + // target didn't handle ACTION_CANCEL. not much we can do + // but they should have. + } + // clear the target + mMotionTarget = null; + // Don't dispatch this event to our own view, because we already + // saw it when intercepting; we just want to give the following + // event to the normal onTouchEvent(). + return true; + } + + if (isUpOrCancel) { + mMotionTarget = null; + } + + // finally offset the event to the target's coordinate system and + // dispatch the event. + final float xc = scrolledXFloat - (float) target.mLeft; + final float yc = scrolledYFloat - (float) target.mTop; + ev.setLocation(xc, yc); + + return target.dispatchTouchEvent(ev); + } + + /** + * {@inheritDoc} + */ + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + + if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { + // We're already in this state, assume our ancestors are too + return; + } + + if (disallowIntercept) { + mGroupFlags |= FLAG_DISALLOW_INTERCEPT; + } else { + mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; + } + + // Pass it up to our parent + if (mParent != null) { + mParent.requestDisallowInterceptTouchEvent(disallowIntercept); + } + } + + /** + * Implement this method to intercept all touch screen motion events. This + * allows you to watch events as they are dispatched to your children, and + * take ownership of the current gesture at any point. + * + * <p>Using this function takes some care, as it has a fairly complicated + * interaction with {@link View#onTouchEvent(MotionEvent) + * View.onTouchEvent(MotionEvent)}, and using it requires implementing + * that method as well as this one in the correct way. Events will be + * received in the following order: + * + * <ol> + * <li> You will receive the down event here. + * <li> The down event will be handled either by a child of this view + * group, or given to your own onTouchEvent() method to handle; this means + * you should implement onTouchEvent() to return true, so you will + * continue to see the rest of the gesture (instead of looking for + * a parent view to handle it). Also, by returning true from + * onTouchEvent(), you will not receive any following + * events in onInterceptTouchEvent() and all touch processing must + * happen in onTouchEvent() like normal. + * <li> For as long as you return false from this function, each following + * event (up to and including the final up) will be delivered first here + * and then to the target's onTouchEvent(). + * <li> If you return true from here, you will not receive any + * following events: the target view will receive the same event but + * with the action {@link MotionEvent#ACTION_CANCEL}, and all further + * events will be delivered to your onTouchEvent() method and no longer + * appear here. + * </ol> + * + * @param ev The motion event being dispatched down the hierarchy. + * @return Return true to steal motion events from the children and have + * them dispatched to this ViewGroup through onTouchEvent(). + * The current target will receive an ACTION_CANCEL event, and no further + * messages will be delivered here. + */ + public boolean onInterceptTouchEvent(MotionEvent ev) { + return false; + } + + /** + * {@inheritDoc} + * + * Looks for a view to give focus to respecting the setting specified by + * {@link #getDescendantFocusability()}. + * + * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to + * find focus within the children of this group when appropriate. + * + * @see #FOCUS_BEFORE_DESCENDANTS + * @see #FOCUS_AFTER_DESCENDANTS + * @see #FOCUS_BLOCK_DESCENDANTS + * @see #onRequestFocusInDescendants + */ + @Override + public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + if (DBG) { + System.out.println(this + " ViewGroup.requestFocus direction=" + + direction); + } + int descendantFocusability = getDescendantFocusability(); + + switch (descendantFocusability) { + case FOCUS_BLOCK_DESCENDANTS: + return super.requestFocus(direction, previouslyFocusedRect); + case FOCUS_BEFORE_DESCENDANTS: { + final boolean took = super.requestFocus(direction, previouslyFocusedRect); + return took ? took : onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + case FOCUS_AFTER_DESCENDANTS: { + final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect); + return took ? took : super.requestFocus(direction, previouslyFocusedRect); + } + default: + throw new IllegalStateException("descendant focusability must be " + + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS " + + "but is " + descendantFocusability); + } + } + + /** + * Look for a descendant to call {@link View#requestFocus} on. + * Called by {@link ViewGroup#requestFocus(int, android.graphics.Rect)} + * when it wants to request focus within its children. Override this to + * customize how your {@link ViewGroup} requests focus within its children. + * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT + * @param previouslyFocusedRect The rectangle (in this View's coordinate system) + * to give a finer grained hint about where focus is coming from. May be null + * if there is no hint. + * @return Whether focus was taken. + */ + @SuppressWarnings({"ConstantConditions"}) + protected boolean onRequestFocusInDescendants(int direction, + Rect previouslyFocusedRect) { + int index; + int increment; + int end; + int count = mChildrenCount; + if ((direction & FOCUS_FORWARD) != 0) { + index = 0; + increment = 1; + end = count; + } else { + index = count - 1; + increment = -1; + end = -1; + } + final View[] children = mChildren; + for (int i = index; i != end; i += increment) { + View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + if (child.requestFocus(direction, previouslyFocusedRect)) { + return true; + } + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + void dispatchAttachedToWindow(AttachInfo info, int visibility) { + super.dispatchAttachedToWindow(info, visibility); + visibility |= mViewFlags & VISIBILITY_MASK; + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchAttachedToWindow(info, visibility); + } + } + + /** + * {@inheritDoc} + */ + @Override + void dispatchDetachedFromWindow() { + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchDetachedFromWindow(); + } + super.dispatchDetachedFromWindow(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setPadding(int left, int top, int right, int bottom) { + super.setPadding(left, top, right, bottom); + + if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingRight) != 0) { + mGroupFlags |= FLAG_PADDING_NOT_NULL; + } else { + mGroupFlags &= ~FLAG_PADDING_NOT_NULL; + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { + super.dispatchSaveInstanceState(container); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchSaveInstanceState(container); + } + } + + /** + * Perform dispatching of a {@link #saveHierarchyState freeze()} to only this view, + * not to its children. For use when overriding + * {@link #dispatchSaveInstanceState dispatchFreeze()} to allow subclasses to freeze + * their own state but not the state of their children. + * + * @param container the container + */ + protected void dispatchFreezeSelfOnly(SparseArray<Parcelable> container) { + super.dispatchSaveInstanceState(container); + } + + /** + * {@inheritDoc} + */ + @Override + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + super.dispatchRestoreInstanceState(container); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchRestoreInstanceState(container); + } + } + + /** + * Perform dispatching of a {@link #restoreHierarchyState thaw()} to only this view, + * not to its children. For use when overriding + * {@link #dispatchRestoreInstanceState dispatchThaw()} to allow subclasses to thaw + * their own state but not the state of their children. + * + * @param container the container + */ + protected void dispatchThawSelfOnly(SparseArray<Parcelable> container) { + super.dispatchRestoreInstanceState(container); + } + + /** + * Enables or disables the drawing cache for each child of this view group. + * + * @param enabled true to enable the cache, false to dispose of it + */ + protected void setChildrenDrawingCacheEnabled(boolean enabled) { + if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) { + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = 0; i < count; i++) { + children[i].setDrawingCacheEnabled(enabled); + } + } + } + + @Override + protected void onAnimationStart() { + super.onAnimationStart(); + + // When this ViewGroup's animation starts, build the cache for the children + if ((mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE) { + final int count = mChildrenCount; + final View[] children = mChildren; + + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + child.setDrawingCacheEnabled(true); + child.buildDrawingCache(); + } + } + + mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE; + } + } + + @Override + protected void onAnimationEnd() { + super.onAnimationEnd(); + + // When this ViewGroup's animation ends, destroy the cache of the children + if ((mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE) { + mGroupFlags &= ~FLAG_CHILDREN_DRAWN_WITH_CACHE; + + if ((mPersistentDrawingCache & PERSISTENT_ANIMATION_CACHE) == 0) { + setChildrenDrawingCacheEnabled(false); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void dispatchDraw(Canvas canvas) { + final int count = mChildrenCount; + final View[] children = mChildren; + int flags = mGroupFlags; + + if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { + final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; + + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + final LayoutParams params = child.getLayoutParams(); + attachLayoutAnimationParameters(child, params, i, count); + bindLayoutAnimation(child); + if (cache) { + child.setDrawingCacheEnabled(true); + child.buildDrawingCache(); + } + } + } + + final LayoutAnimationController controller = mLayoutAnimationController; + if (controller.willOverlap()) { + mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; + } + + controller.start(); + + mGroupFlags &= ~FLAG_RUN_ANIMATION; + mGroupFlags &= ~FLAG_ANIMATION_DONE; + + if (cache) { + mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE; + } + + if (mAnimationListener != null) { + mAnimationListener.onAnimationStart(controller.getAnimation()); + } + } + + int saveCount = 0; + final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; + if (clipToPadding) { + saveCount = canvas.save(); + final int scrollX = mScrollX; + final int scrollY = mScrollY; + canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, + scrollX + mRight - mLeft - mPaddingRight, + scrollY + mBottom - mTop - mPaddingBottom); + + } + + mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED; + + boolean more = false; + final long drawingTime = getDrawingTime(); + + if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { + more |= drawChild(canvas, child, drawingTime); + } + } + } else { + for (int i = 0; i < count; i++) { + final View child = children[getChildDrawingOrder(count, i)]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { + more |= drawChild(canvas, child, drawingTime); + } + } + } + + // Draw any disappearing views that have animations + if (mDisappearingChildren != null) { + final ArrayList<View> disappearingChildren = mDisappearingChildren; + final int disappearingCount = disappearingChildren.size() - 1; + // Go backwards -- we may delete as animations finish + for (int i = disappearingCount; i >= 0; i--) { + final View child = disappearingChildren.get(i); + more |= drawChild(canvas, child, drawingTime); + } + } + + if (clipToPadding) { + canvas.restoreToCount(saveCount); + } + + // mGroupFlags might have been updated by drawChild() + flags = mGroupFlags; + + if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) { + invalidate(); + } + + if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 && + mLayoutAnimationController.isDone() && !more) { + // We want to erase the drawing cache and notify the listener after the + // next frame is drawn because one extra invalidate() is caused by + // drawChild() after the animation is over + mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER; + final Runnable end = new Runnable() { + public void run() { + notifyAnimationListener(); + } + }; + post(end); + } + } + + /** + * Returns the index of the child to draw for this iteration. Override this + * if you want to change the drawing order of children. By default, it + * returns i. + * <p> + * NOTE: In order for this method to be called, the + * {@link #FLAG_USE_CHILD_DRAWING_ORDER} must be set. + * + * @param i The current iteration. + * @return The index of the child to draw this iteration. + */ + protected int getChildDrawingOrder(int childCount, int i) { + return i; + } + + private void notifyAnimationListener() { + mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER; + mGroupFlags |= FLAG_ANIMATION_DONE; + + if (mAnimationListener != null) { + final Runnable end = new Runnable() { + public void run() { + mAnimationListener.onAnimationEnd(mLayoutAnimationController.getAnimation()); + } + }; + post(end); + } + + if ((mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE) { + mGroupFlags &= ~FLAG_CHILDREN_DRAWN_WITH_CACHE; + if ((mPersistentDrawingCache & PERSISTENT_ANIMATION_CACHE) == 0) { + setChildrenDrawingCacheEnabled(false); + } + } + + invalidate(); + } + + /** + * Draw one child of this View Group. This method is responsible for getting + * the canvas in the right state. This includes clipping, translating so + * that the child's scrolled origin is at 0, 0, and applying any animation + * transformations. + * + * @param canvas The canvas on which to draw the child + * @param child Who to draw + * @param drawingTime The time at which draw is occuring + * @return True if an invalidate() was issued + */ + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean more = false; + + final int cl = child.mLeft; + final int ct = child.mTop; + final int cr = child.mRight; + final int cb = child.mBottom; + + final int flags = mGroupFlags; + + if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) { + mChildTransformation.clear(); + mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION; + } + + Transformation transformToApply = null; + final Animation a = child.getAnimation(); + boolean concatMatrix = false; + + if (a != null) { + if (!a.isInitialized()) { + a.initialize(cr - cl, cb - ct, getWidth(), getHeight()); + child.onAnimationStart(); + } + + more = a.getTransformation(drawingTime, mChildTransformation); + transformToApply = mChildTransformation; + + concatMatrix = a.willChangeTransformationMatrix(); + + if (more) { + if (!a.willChangeBounds()) { + if ((flags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) == + FLAG_OPTIMIZE_INVALIDATE) { + mGroupFlags |= FLAG_INVALIDATE_REQUIRED; + } else if ((flags & FLAG_INVALIDATE_REQUIRED) == 0) { + invalidate(cl, ct, cr, cb); + } + } else { + mGroupFlags |= FLAG_INVALIDATE_REQUIRED; + } + } + } else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) == + FLAG_SUPPORT_STATIC_TRANSFORMATIONS) { + final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation); + if (hasTransform) { + final int transformType = mChildTransformation.getTransformationType(); + transformToApply = transformType != Transformation.TYPE_IDENTITY ? + mChildTransformation : null; + concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0; + } + } + + if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW)) { + return more; + } + + child.computeScroll(); + + final int sx = child.mScrollX; + final int sy = child.mScrollY; + + Bitmap cache = null; + if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE || + (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) { + cache = child.getDrawingCache(); + } + + final boolean hasNoCache = cache == null; + + final int restoreTo = canvas.save(); + if (hasNoCache) { + canvas.translate(cl - sx, ct - sy); + } else { + canvas.translate(cl, ct); + } + + float alpha = 1.0f; + + if (transformToApply != null) { + if (concatMatrix) { + int transX = 0; + int transY = 0; + if (hasNoCache) { + transX = -sx; + transY = -sy; + } + // Undo the scroll translation, apply the transformation matrix, + // then redo the scroll translate to get the correct result. + canvas.translate(-transX, -transY); + canvas.concat(transformToApply.getMatrix()); + canvas.translate(transX, transY); + mGroupFlags |= FLAG_CLEAR_TRANSFORMATION; + } + + alpha = transformToApply.getAlpha(); + if (alpha < 1.0f) { + mGroupFlags |= FLAG_CLEAR_TRANSFORMATION; + } + + if (alpha < 1.0f && hasNoCache) { + final int multipliedAlpha = (int) (255 * alpha); + if (!child.onSetAlpha(multipliedAlpha)) { + canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct, multipliedAlpha, + Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); + } else { + child.mPrivateFlags |= ALPHA_SET; + } + } + } else if ((child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) { + child.onSetAlpha(255); + } + + if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { + if (hasNoCache) { + canvas.clipRect(sx, sy, sx + cr - cl, sy + cb - ct); + } else { + canvas.clipRect(0, 0, cr - cl, cb - ct); + } + } + + if (hasNoCache) { + // Fast path for layouts with no backgrounds + if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + child.mPrivateFlags |= DRAWN; + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); + } + child.dispatchDraw(canvas); + } else { + child.draw(canvas); + } + } else { + final Paint cachePaint = mCachePaint; + if (alpha < 1.0f) { + cachePaint.setAlpha((int) (alpha * 255)); + mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE; + } else if ((flags & FLAG_ALPHA_LOWER_THAN_ONE) == FLAG_ALPHA_LOWER_THAN_ONE) { + cachePaint.setAlpha(255); + mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE; + } + child.mPrivateFlags |= DRAWN; + if (ViewRoot.PROFILE_DRAWING) { + EventLog.writeEvent(60003, hashCode()); + } + canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint); + } + + canvas.restoreToCount(restoreTo); + + if (a != null && !more) { + child.onSetAlpha(255); + finishAnimatingView(child, a); + } + + return more; + } + + /** + * By default, children are clipped to their bounds before drawing. This + * allows view groups to override this behavior for animations, etc. + * + * @param clipChildren true to clip children to their bounds, + * false otherwise + * @attr ref android.R.styleable#ViewGroup_clipChildren + */ + public void setClipChildren(boolean clipChildren) { + setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren); + } + + /** + * By default, children are clipped to the padding of the ViewGroup. This + * allows view groups to override this behavior + * + * @param clipToPadding true to clip children to the padding of the + * group, false otherwise + * @attr ref android.R.styleable#ViewGroup_clipToPadding + */ + public void setClipToPadding(boolean clipToPadding) { + setBooleanFlag(FLAG_CLIP_TO_PADDING, clipToPadding); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispatchSetSelected(boolean selected) { + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = 0; i < count; i++) { + children[i].setSelected(selected); + } + } + + @Override + protected void dispatchSetPressed(boolean pressed) { + final View[] children = mChildren; + final int count = mChildrenCount; + for (int i = 0; i < count; i++) { + children[i].setPressed(pressed); + } + } + + /** + * {@inheritDoc} + */ + protected boolean getChildStaticTransformation(View child, Transformation t) { + return false; + } + + /** + * {@hide} + */ + @Override + protected View findViewTraversal(int id) { + if (id == mID) { + return this; + } + + final View[] where = mChildren; + final int len = mChildrenCount; + + for (int i = 0; i < len; i++) { + View v = where[i]; + + if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + v = v.findViewById(id); + + if (v != null) { + return v; + } + } + } + + return null; + } + + /** + * {@hide} + */ + @Override + protected View findViewWithTagTraversal(Object tag) { + if (tag != null && tag.equals(mTag)) { + return this; + } + + final View[] where = mChildren; + final int len = mChildrenCount; + + for (int i = 0; i < len; i++) { + View v = where[i]; + + if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + v = v.findViewWithTag(tag); + + if (v != null) { + return v; + } + } + } + + return null; + } + + /** + * Adds a child view. If no layout parameters are already set on the child, the + * default parameters for this ViewGroup are set on the child. + * + * @param child the child view to add + * + * @see #generateDefaultLayoutParams() + */ + public void addView(View child) { + addView(child, -1); + } + + /** + * Adds a child view. If no layout parameters are already set on the child, the + * default parameters for this ViewGroup are set on the child. + * + * @param child the child view to add + * @param index the position at which to add the child + * + * @see #generateDefaultLayoutParams() + */ + public void addView(View child, int index) { + LayoutParams params = child.getLayoutParams(); + if (params == null) { + params = generateDefaultLayoutParams(); + if (params == null) { + throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); + } + } + addView(child, index, params); + } + + /** + * Adds a child view with this ViewGroup's default layout parameters and the + * specified width and height. + * + * @param child the child view to add + */ + public void addView(View child, int width, int height) { + final LayoutParams params = generateDefaultLayoutParams(); + params.width = width; + params.height = height; + addView(child, -1, params); + } + + /** + * Adds a child view with the specified layout parameters. + * + * @param child the child view to add + * @param params the layout parameters to set on the child + */ + public void addView(View child, LayoutParams params) { + addView(child, -1, params); + } + + /** + * Adds a child view with the specified layout parameters. + * + * @param child the child view to add + * @param index the position at which to add the child + * @param params the layout parameters to set on the child + */ + public void addView(View child, int index, LayoutParams params) { + if (DBG) { + System.out.println(this + " addView"); + } + + // addViewInner() will call child.requestLayout() when setting the new LayoutParams + // therefore, we call requestLayout() on ourselves before, so that the child's request + // will be blocked at our level + requestLayout(); + invalidate(); + addViewInner(child, index, params, false); + } + + /** + * {@inheritDoc} + */ + public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + if (!checkLayoutParams(params)) { + throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this); + } + if (view.mParent != this) { + throw new IllegalArgumentException("Given view not a child of " + this); + } + view.setLayoutParams(params); + } + + /** + * {@inheritDoc} + */ + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p != null; + } + + /** + * Interface definition for a callback to be invoked when the hierarchy + * within this view changed. The hierarchy changes whenever a child is added + * to or removed from this view. + */ + public interface OnHierarchyChangeListener { + /** + * Called when a new child is added to a parent view. + * + * @param parent the view in which a child was added + * @param child the new child view added in the hierarchy + */ + void onChildViewAdded(View parent, View child); + + /** + * Called when a child is removed from a parent view. + * + * @param parent the view from which the child was removed + * @param child the child removed from the hierarchy + */ + void onChildViewRemoved(View parent, View child); + } + + /** + * Register a callback to be invoked when a child is added to or removed + * from this view. + * + * @param listener the callback to invoke on hierarchy change + */ + public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { + mOnHierarchyChangeListener = listener; + } + + /** + * Adds a view during layout. This is useful if in your onLayout() method, + * you need to add more views (as does the list view for example). + * + * If index is negative, it means put it at the end of the list. + * + * @param child the view to add to the group + * @param index the index at which the child must be added + * @param params the layout parameters to associate with the child + * @return true if the child was added, false otherwise + */ + protected boolean addViewInLayout(View child, int index, LayoutParams params) { + return addViewInLayout(child, index, params, false); + } + + /** + * Adds a view during layout. This is useful if in your onLayout() method, + * you need to add more views (as does the list view for example). + * + * If index is negative, it means put it at the end of the list. + * + * @param child the view to add to the group + * @param index the index at which the child must be added + * @param params the layout parameters to associate with the child + * @param preventRequestLayout if true, calling this method will not trigger a + * layout request on child + * @return true if the child was added, false otherwise + */ + protected boolean addViewInLayout(View child, int index, LayoutParams params, + boolean preventRequestLayout) { + child.mParent = null; + addViewInner(child, index, params, preventRequestLayout); + child.mPrivateFlags |= DRAWN; + return true; + } + + /** + * Prevents the specified child to be laid out during the next layout pass. + * + * @param child the child on which to perform the cleanup + */ + protected void cleanupLayoutState(View child) { + child.mPrivateFlags &= ~View.FORCE_LAYOUT; + } + + private void addViewInner(View child, int index, LayoutParams params, + boolean preventRequestLayout) { + + if (child.getParent() != null) { + throw new IllegalStateException("The specified child already has a parent. " + + "You must call removeView() on the child's parent first."); + } + + if (!checkLayoutParams(params)) { + params = generateLayoutParams(params); + } + + if (preventRequestLayout) { + child.mLayoutParams = params; + } else { + child.setLayoutParams(params); + } + + if (index < 0) { + index = mChildrenCount; + } + + addInArray(child, index); + + // tell our children + if (preventRequestLayout) { + child.assignParent(this); + } else { + child.mParent = this; + } + + if (child.hasFocus()) { + requestChildFocus(child, child.findFocus()); + } + + AttachInfo ai = mAttachInfo; + if (ai != null) { + boolean lastKeepOn = ai.mKeepScreenOn; + ai.mKeepScreenOn = false; + child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK)); + if (ai.mKeepScreenOn) { + needGlobalAttributesUpdate(true); + } + ai.mKeepScreenOn = lastKeepOn; + } + + if (mOnHierarchyChangeListener != null) { + mOnHierarchyChangeListener.onChildViewAdded(this, child); + } + + if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) { + mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE; + } + } + + private void addInArray(View child, int index) { + View[] children = mChildren; + final int count = mChildrenCount; + final int size = children.length; + if (index == count) { + if (size == count) { + mChildren = new View[size + ARRAY_CAPACITY_INCREMENT]; + System.arraycopy(children, 0, mChildren, 0, size); + children = mChildren; + } + children[mChildrenCount++] = child; + } else if (index < count) { + if (size == count) { + mChildren = new View[size + ARRAY_CAPACITY_INCREMENT]; + System.arraycopy(children, 0, mChildren, 0, index); + System.arraycopy(children, index, mChildren, index + 1, count - index); + children = mChildren; + } else { + System.arraycopy(children, index, children, index + 1, count - index); + } + children[index] = child; + mChildrenCount++; + } else { + throw new IndexOutOfBoundsException("index=" + index + " count=" + count); + } + } + + // This method also sets the child's mParent to null + private void removeFromArray(int index) { + final View[] children = mChildren; + children[index].mParent = null; + final int count = mChildrenCount; + if (index == count - 1) { + children[--mChildrenCount] = null; + } else if (index >= 0 && index < count) { + System.arraycopy(children, index + 1, children, index, count - index - 1); + children[--mChildrenCount] = null; + } else { + throw new IndexOutOfBoundsException(); + } + } + + // This method also sets the children's mParent to null + private void removeFromArray(int start, int count) { + final View[] children = mChildren; + final int childrenCount = mChildrenCount; + + start = Math.max(0, start); + final int end = Math.min(childrenCount, start + count); + + if (start == end) { + return; + } + + if (end == childrenCount) { + for (int i = start; i < end; i++) { + children[i].mParent = null; + children[i] = null; + } + } else { + for (int i = start; i < end; i++) { + children[i].mParent = null; + } + + // Since we're looping above, we might as well do the copy, but is arraycopy() + // faster than the extra 2 bounds checks we would do in the loop? + System.arraycopy(children, end, children, start, childrenCount - end); + + for (int i = childrenCount - (end - start); i < childrenCount; i++) { + children[i] = null; + } + } + + mChildrenCount -= (end - start); + } + + private void bindLayoutAnimation(View child) { + Animation a = mLayoutAnimationController.getAnimationForView(child); + child.setAnimation(a); + } + + /** + * Subclasses should override this method to set layout animation + * parameters on the supplied child. + * + * @param child the child to associate with animation parameters + * @param params the child's layout parameters which hold the animation + * parameters + * @param index the index of the child in the view group + * @param count the number of children in the view group + */ + protected void attachLayoutAnimationParameters(View child, + LayoutParams params, int index, int count) { + LayoutAnimationController.AnimationParameters animationParams = + params.layoutAnimationParameters; + if (animationParams == null) { + animationParams = + new LayoutAnimationController.AnimationParameters(); + params.layoutAnimationParameters = animationParams; + } + + animationParams.count = count; + animationParams.index = index; + } + + /** + * {@inheritDoc} + */ + public void removeView(View view) { + removeViewInternal(view); + requestLayout(); + invalidate(); + } + + /** + * Removes a view during layout. This is useful if in your onLayout() method, + * you need to remove more views. + * + * @param view the view to remove from the group + */ + public void removeViewInLayout(View view) { + removeViewInternal(view); + } + + /** + * Removes a range of views during layout. This is useful if in your onLayout() method, + * you need to remove more views. + * + * @param start the index of the first view to remove from the group + * @param count the number of views to remove from the group + */ + public void removeViewsInLayout(int start, int count) { + removeViewsInternal(start, count); + } + + /** + * Removes the view at the specified position in the group. + * + * @param index the position in the group of the view to remove + */ + public void removeViewAt(int index) { + removeViewInternal(index, getChildAt(index)); + requestLayout(); + invalidate(); + } + + /** + * Removes the specified range of views from the group. + * + * @param start the first position in the group of the range of views to remove + * @param count the number of views to remove + */ + public void removeViews(int start, int count) { + removeViewsInternal(start, count); + requestLayout(); + invalidate(); + } + + private void removeViewInternal(View view) { + final int index = indexOfChild(view); + if (index >= 0) { + removeViewInternal(index, view); + } + } + + private void removeViewInternal(int index, View view) { + boolean clearChildFocus = false; + if (view == mFocused) { + view.clearFocusForRemoval(); + clearChildFocus = true; + } + + if (view.getAnimation() != null) { + addDisappearingView(view); + } else if (mAttachInfo != null) { + view.dispatchDetachedFromWindow(); + } + + if (mOnHierarchyChangeListener != null) { + mOnHierarchyChangeListener.onChildViewRemoved(this, view); + } + + needGlobalAttributesUpdate(false); + + removeFromArray(index); + + if (clearChildFocus) { + clearChildFocus(view); + } + } + + private void removeViewsInternal(int start, int count) { + final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener; + final boolean notifyListener = onHierarchyChangeListener != null; + final View focused = mFocused; + final boolean detach = mAttachInfo != null; + View clearChildFocus = null; + + final View[] children = mChildren; + final int end = start + count; + + for (int i = start; i < end; i++) { + final View view = children[i]; + + if (view == focused) { + view.clearFocusForRemoval(); + clearChildFocus = view; + } + + if (view.getAnimation() != null) { + addDisappearingView(view); + } else if (detach) { + view.dispatchDetachedFromWindow(); + } + + needGlobalAttributesUpdate(false); + + if (notifyListener) { + onHierarchyChangeListener.onChildViewRemoved(this, view); + } + } + + removeFromArray(start, count); + + if (clearChildFocus != null) { + clearChildFocus(clearChildFocus); + } + } + + /** + * Call this method to remove all child views from the + * ViewGroup. + */ + public void removeAllViews() { + removeAllViewsInLayout(); + requestLayout(); + invalidate(); + } + + /** + * Called by a ViewGroup subclass to remove child views from itself, + * when it must first know its size on screen before it can calculate how many + * child views it will render. An example is a Gallery or a ListView, which + * may "have" 50 children, but actually only render the number of children + * that can currently fit inside the object on screen. Do not call + * this method unless you are extending ViewGroup and understand the + * view measuring and layout pipeline. + */ + public void removeAllViewsInLayout() { + final int count = mChildrenCount; + if (count <= 0) { + return; + } + + final View[] children = mChildren; + mChildrenCount = 0; + + final OnHierarchyChangeListener listener = mOnHierarchyChangeListener; + final boolean notify = listener != null; + final View focused = mFocused; + final boolean detach = mAttachInfo != null; + View clearChildFocus = null; + + needGlobalAttributesUpdate(false); + + for (int i = count - 1; i >= 0; i--) { + final View view = children[i]; + + if (view == focused) { + view.clearFocusForRemoval(); + clearChildFocus = view; + } + + if (view.getAnimation() != null) { + addDisappearingView(view); + } else if (detach) { + view.dispatchDetachedFromWindow(); + } + + if (notify) { + listener.onChildViewRemoved(this, view); + } + + view.mParent = null; + children[i] = null; + } + + if (clearChildFocus != null) { + clearChildFocus(clearChildFocus); + } + } + + /** + * Finishes the removal of a detached view. This method will dispatch the detached from + * window event and notify the hierarchy change listener. + * + * @param child the child to be definitely removed from the view hierarchy + * @param animate if true and the view has an animation, the view is placed in the + * disappearing views list, otherwise, it is detached from the window + * + * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) + * @see #detachAllViewsFromParent() + * @see #detachViewFromParent(View) + * @see #detachViewFromParent(int) + */ + protected void removeDetachedView(View child, boolean animate) { + if (child == mFocused) { + child.clearFocus(); + } + + if (animate && child.getAnimation() != null) { + addDisappearingView(child); + } else if (mAttachInfo != null) { + child.dispatchDetachedFromWindow(); + } + + if (mOnHierarchyChangeListener != null) { + mOnHierarchyChangeListener.onChildViewRemoved(this, child); + } + } + + /** + * Attaches a view to this view group. Attaching a view assigns this group as the parent, + * sets the layout parameters and puts the view in the list of children so it can be retrieved + * by calling {@link #getChildAt(int)}. + * + * This method should be called only for view which were detached from their parent. + * + * @param child the child to attach + * @param index the index at which the child should be attached + * @param params the layout parameters of the child + * + * @see #removeDetachedView(View, boolean) + * @see #detachAllViewsFromParent() + * @see #detachViewFromParent(View) + * @see #detachViewFromParent(int) + */ + protected void attachViewToParent(View child, int index, LayoutParams params) { + child.mLayoutParams = params; + + if (index < 0) { + index = mChildrenCount; + } + + addInArray(child, index); + + child.mParent = this; + child.mPrivateFlags |= DRAWN; + + if (child.hasFocus()) { + requestChildFocus(child, child.findFocus()); + } + } + + /** + * Detaches a view from its parent. Detaching a view should be temporary and followed + * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, + * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * + * @param child the child to detach + * + * @see #detachViewFromParent(int) + * @see #detachViewsFromParent(int, int) + * @see #detachAllViewsFromParent() + * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) + * @see #removeDetachedView(View, boolean) + */ + protected void detachViewFromParent(View child) { + removeFromArray(indexOfChild(child)); + } + + /** + * Detaches a view from its parent. Detaching a view should be temporary and followed + * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, + * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * + * @param index the index of the child to detach + * + * @see #detachViewFromParent(View) + * @see #detachAllViewsFromParent() + * @see #detachViewsFromParent(int, int) + * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) + * @see #removeDetachedView(View, boolean) + */ + protected void detachViewFromParent(int index) { + removeFromArray(index); + } + + /** + * Detaches a range of view from their parent. Detaching a view should be temporary and followed + * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, its + * parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * + * @param start the first index of the childrend range to detach + * @param count the number of children to detach + * + * @see #detachViewFromParent(View) + * @see #detachViewFromParent(int) + * @see #detachAllViewsFromParent() + * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) + * @see #removeDetachedView(View, boolean) + */ + protected void detachViewsFromParent(int start, int count) { + removeFromArray(start, count); + } + + /** + * Detaches all views from theparent. Detaching a view should be temporary and followed + * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} + * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, + * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}. + * + * @see #detachViewFromParent(View) + * @see #detachViewFromParent(int) + * @see #detachViewsFromParent(int, int) + * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) + * @see #removeDetachedView(View, boolean) + */ + protected void detachAllViewsFromParent() { + final int count = mChildrenCount; + if (count <= 0) { + return; + } + + final View[] children = mChildren; + mChildrenCount = 0; + + for (int i = count - 1; i >= 0; i--) { + children[i].mParent = null; + children[i] = null; + } + } + + /** + * Don't call or override this method. It is used for the implementation of + * the view hierarchy. + */ + public final void invalidateChild(View child, final Rect dirty) { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD); + } + + ViewParent parent = this; + + final int[] location = mLocation; + location[CHILD_LEFT_INDEX] = child.mLeft; + location[CHILD_TOP_INDEX] = child.mTop; + + do { + parent = parent.invalidateChildInParent(location, dirty); + } while (parent != null); + } + + /** + * Don't call or override this method. It is used for the implementation of + * the view hierarchy. + * + * This implementation returns null if this ViewGroup does not have a parent, + * if this ViewGroup is already fully invalidated or if the dirty rectangle + * does not intersect with this ViewGroup's bounds. + */ + public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { + if (ViewDebug.TRACE_HIERARCHY) { + ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE_CHILD_IN_PARENT); + } + + if ((mPrivateFlags & DRAWN) == DRAWN) { + if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) != + FLAG_OPTIMIZE_INVALIDATE) { + dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, + location[CHILD_TOP_INDEX] - mScrollY); + + final int left = mLeft; + final int top = mTop; + + if (dirty.intersect(0, 0, mRight - left, mBottom - top)) { + mPrivateFlags &= ~DRAWING_CACHE_VALID; + + location[CHILD_LEFT_INDEX] = left; + location[CHILD_TOP_INDEX] = top; + + return mParent; + } + } else { + mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID; + + location[CHILD_LEFT_INDEX] = mLeft; + location[CHILD_TOP_INDEX] = mTop; + + dirty.set(0, 0, mRight - location[CHILD_LEFT_INDEX], + mBottom - location[CHILD_TOP_INDEX]); + + return mParent; + } + } + + return null; + } + + /** + * Offset a rectangle that is in a descendant's coordinate + * space into our coordinate space. + * @param descendant A descendant of this view + * @param rect A rectangle defined in descendant's coordinate space. + */ + public final void offsetDescendantRectToMyCoords(View descendant, Rect rect) { + offsetRectBetweenParentAndChild(descendant, rect, true, false); + } + + /** + * Offset a rectangle that is in our coordinate space into an ancestor's + * coordinate space. + * @param descendant A descendant of this view + * @param rect A rectangle defined in descendant's coordinate space. + */ + public final void offsetRectIntoDescendantCoords(View descendant, Rect rect) { + offsetRectBetweenParentAndChild(descendant, rect, false, false); + } + + /** + * Helper method that offsets a rect either from parent to descendant or + * descendant to parent. + */ + void offsetRectBetweenParentAndChild(View descendant, Rect rect, + boolean offsetFromChildToParent, boolean clipToBounds) { + + // already in the same coord system :) + if (descendant == this) { + return; + } + + ViewParent theParent = descendant.mParent; + + // search and offset up to the parent + while ((theParent != null) + && (theParent instanceof View) + && (theParent != this)) { + + if (offsetFromChildToParent) { + rect.offset(descendant.mLeft - descendant.mScrollX, + descendant.mTop - descendant.mScrollY); + if (clipToBounds) { + View p = (View) theParent; + rect.intersect(0, 0, p.mRight - p.mLeft, p.mBottom - p.mTop); + } + } else { + if (clipToBounds) { + View p = (View) theParent; + rect.intersect(0, 0, p.mRight - p.mLeft, p.mBottom - p.mTop); + } + rect.offset(descendant.mScrollX - descendant.mLeft, + descendant.mScrollY - descendant.mTop); + } + + descendant = (View) theParent; + theParent = descendant.mParent; + } + + // now that we are up to this view, need to offset one more time + // to get into our coordinate space + if (theParent == this) { + if (offsetFromChildToParent) { + rect.offset(descendant.mLeft - descendant.mScrollX, + descendant.mTop - descendant.mScrollY); + } else { + rect.offset(descendant.mScrollX - descendant.mLeft, + descendant.mScrollY - descendant.mTop); + } + } else { + throw new IllegalArgumentException("parameter must be a descendant of this view"); + } + } + + /** + * Offset the vertical location of all children of this view by the specified number of pixels. + * + * @param offset the number of pixels to offset + * + * @hide + */ + public void offsetChildrenTopAndBottom(int offset) { + final int count = mChildrenCount; + final View[] children = mChildren; + + for (int i = 0; i < count; i++) { + final View v = children[i]; + v.mTop += offset; + v.mBottom += offset; + } + } + + /** + * {@inheritDoc} + */ + public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { + int dx = child.mLeft - mScrollX; + int dy = child.mTop - mScrollY; + if (offset != null) { + offset.x += dx; + offset.y += dy; + } + r.offset(dx, dy); + return r.intersect(0, 0, mRight - mLeft, mBottom - mTop) && + (mParent == null || mParent.getChildVisibleRect(this, r, offset)); + } + + /** + * {@inheritDoc} + */ + @Override + protected abstract void onLayout(boolean changed, + int l, int t, int r, int b); + + /** + * Indicates whether the view group has the ability to animate its children + * after the first layout. + * + * @return true if the children can be animated, false otherwise + */ + protected boolean canAnimate() { + return mLayoutAnimationController != null; + } + + /** + * Runs the layout animation. Calling this method triggers a relayout of + * this view group. + */ + public void startLayoutAnimation() { + if (mLayoutAnimationController != null) { + mGroupFlags |= FLAG_RUN_ANIMATION; + requestLayout(); + } + } + + /** + * Schedules the layout animation to be played after the next layout pass + * of this view group. This can be used to restart the layout animation + * when the content of the view group changes or when the activity is + * paused and resumed. + */ + public void scheduleLayoutAnimation() { + mGroupFlags |= FLAG_RUN_ANIMATION; + } + + /** + * Sets the layout animation controller used to animate the group's + * children after the first layout. + * + * @param controller the animation controller + */ + public void setLayoutAnimation(LayoutAnimationController controller) { + mLayoutAnimationController = controller; + if (mLayoutAnimationController != null) { + mGroupFlags |= FLAG_RUN_ANIMATION; + } + } + + /** + * Returns the layout animation controller used to animate the group's + * children. + * + * @return the current animation controller + */ + public LayoutAnimationController getLayoutAnimation() { + return mLayoutAnimationController; + } + + /** + * Indicates whether the children's drawing cache is used during a layout + * animation. By default, the drawing cache is enabled but this will prevent + * nested layout animations from working. To nest animations, you must disable + * the cache. + * + * @return true if the animation cache is enabled, false otherwise + * + * @see #setAnimationCacheEnabled(boolean) + * @see View#setDrawingCacheEnabled(boolean) + */ + @ViewDebug.ExportedProperty + public boolean isAnimationCacheEnabled() { + return (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; + } + + /** + * Enables or disables the children's drawing cache during a layout animation. + * By default, the drawing cache is enabled but this will prevent nested + * layout animations from working. To nest animations, you must disable the + * cache. + * + * @param enabled true to enable the animation cache, false otherwise + * + * @see #isAnimationCacheEnabled() + * @see View#setDrawingCacheEnabled(boolean) + */ + public void setAnimationCacheEnabled(boolean enabled) { + setBooleanFlag(FLAG_ANIMATION_CACHE, enabled); + } + + /** + * Indicates whether this ViewGroup will always try to draw its children using their + * drawing cache. By default this property is enabled. + * + * @return true if the animation cache is enabled, false otherwise + * + * @see #setAlwaysDrawnWithCacheEnabled(boolean) + * @see #setChildrenDrawnWithCacheEnabled(boolean) + * @see View#setDrawingCacheEnabled(boolean) + */ + @ViewDebug.ExportedProperty + public boolean isAlwaysDrawnWithCacheEnabled() { + return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE; + } + + /** + * Indicates whether this ViewGroup will always try to draw its children using their + * drawing cache. This property can be set to true when the cache rendering is + * slightly different from the children's normal rendering. Renderings can be different, + * for instance, when the cache's quality is set to low. + * + * When this property is disabled, the ViewGroup will use the drawing cache of its + * children only when asked to. It's usually the task of subclasses to tell ViewGroup + * when to start using the drawing cache and when to stop using it. + * + * @param always true to always draw with the drawing cache, false otherwise + * + * @see #isAlwaysDrawnWithCacheEnabled() + * @see #setChildrenDrawnWithCacheEnabled(boolean) + * @see View#setDrawingCacheEnabled(boolean) + * @see View#setDrawingCacheQuality(int) + */ + public void setAlwaysDrawnWithCacheEnabled(boolean always) { + setBooleanFlag(FLAG_ALWAYS_DRAWN_WITH_CACHE, always); + } + + /** + * Indicates whether the ViewGroup is currently drawing its children using + * their drawing cache. + * + * @return true if children should be drawn with their cache, false otherwise + * + * @see #setAlwaysDrawnWithCacheEnabled(boolean) + * @see #setChildrenDrawnWithCacheEnabled(boolean) + */ + @ViewDebug.ExportedProperty + protected boolean isChildrenDrawnWithCacheEnabled() { + return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE; + } + + /** + * Tells the ViewGroup to draw its children using their drawing cache. This property + * is ignored when {@link #isAlwaysDrawnWithCacheEnabled()} is true. A child's drawing cache + * will be used only if it has been enabled. + * + * Subclasses should call this method to start and stop using the drawing cache when + * they perform performance sensitive operations, like scrolling or animating. + * + * @param enabled true if children should be drawn with their cache, false otherwise + * + * @see #setAlwaysDrawnWithCacheEnabled(boolean) + * @see #isChildrenDrawnWithCacheEnabled() + */ + protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { + setBooleanFlag(FLAG_CHILDREN_DRAWN_WITH_CACHE, enabled); + } + + private void setBooleanFlag(int flag, boolean value) { + if (value) { + mGroupFlags |= flag; + } else { + mGroupFlags &= ~flag; + } + } + + /** + * Returns an integer indicating what types of drawing caches are kept in memory. + * + * @see #setPersistentDrawingCache(int) + * @see #setAnimationCacheEnabled(boolean) + * + * @return one or a combination of {@link #PERSISTENT_NO_CACHE}, + * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} + * and {@link #PERSISTENT_ALL_CACHES} + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"), + @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES, to = "ANIMATION"), + @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"), + @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES, to = "ALL") + }) + public int getPersistentDrawingCache() { + return mPersistentDrawingCache; + } + + /** + * Indicates what types of drawing caches should be kept in memory after + * they have been created. + * + * @see #getPersistentDrawingCache() + * @see #setAnimationCacheEnabled(boolean) + * + * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE}, + * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} + * and {@link #PERSISTENT_ALL_CACHES} + */ + public void setPersistentDrawingCache(int drawingCacheToKeep) { + mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES; + } + + /** + * Returns a new set of layout parameters based on the supplied attributes set. + * + * @param attrs the attributes to build the layout parameters from + * + * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one + * of its descendants + */ + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + /** + * Returns a safe set of layout parameters based on the supplied layout params. + * When a ViewGroup is passed a View whose layout params do not pass the test of + * {@link #checkLayoutParams(android.view.ViewGroup.LayoutParams)}, this method + * is invoked. This method should return a new set of layout params suitable for + * this ViewGroup, possibly by copying the appropriate attributes from the + * specified set of layout params. + * + * @param p The layout parameters to convert into a suitable set of layout parameters + * for this ViewGroup. + * + * @return an instance of {@link android.view.ViewGroup.LayoutParams} or one + * of its descendants + */ + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return p; + } + + /** + * Returns a set of default layout parameters. These parameters are requested + * when the View passed to {@link #addView(View)} has no layout parameters + * already set. If null is returned, an exception is thrown from addView. + * + * @return a set of default layout parameters or null + */ + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + + /** + * {@inheritDoc} + */ + @Override + protected void debug(int depth) { + super.debug(depth); + String output; + + if (mFocused != null) { + output = debugIndent(depth); + output += "mFocused"; + Log.d(VIEW_LOG_TAG, output); + } + if (mChildrenCount != 0) { + output = debugIndent(depth); + output += "{"; + Log.d(VIEW_LOG_TAG, output); + } + int count = mChildrenCount; + for (int i = 0; i < count; i++) { + View child = mChildren[i]; + child.debug(depth + 1); + } + + if (mChildrenCount != 0) { + output = debugIndent(depth); + output += "}"; + Log.d(VIEW_LOG_TAG, output); + } + } + + /** + * Returns the position in the group of the specified child view. + * + * @param child the view for which to get the position + * @return a positive integer representing the position of the view in the + * group, or -1 if the view does not exist in the group + */ + public int indexOfChild(View child) { + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + if (children[i] == child) { + return i; + } + } + return -1; + } + + /** + * Returns the number of children in the group. + * + * @return a positive integer representing the number of children in + * the group + */ + public int getChildCount() { + return mChildrenCount; + } + + /** + * Returns the view at the specified position in the group. + * + * @param index the position at which to get the view from + * @return the view at the specified position or null if the position + * does not exist within the group + */ + public View getChildAt(int index) { + try { + return mChildren[index]; + } catch (IndexOutOfBoundsException ex) { + return null; + } + } + + /** + * Ask all of the children of this view to measure themselves, taking into + * account both the MeasureSpec requirements for this view and its padding. + * We skip children that are in the GONE state The heavy lifting is done in + * getChildMeasureSpec. + * + * @param widthMeasureSpec The width requirements for this view + * @param heightMeasureSpec The height requirements for this view + */ + protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { + final int size = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < size; ++i) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { + measureChild(child, widthMeasureSpec, heightMeasureSpec); + } + } + } + + /** + * Ask one of the children of this view to measure itself, taking into + * account both the MeasureSpec requirements for this view and its padding. + * The heavy lifting is done in getChildMeasureSpec. + * + * @param child The child to measure + * @param parentWidthMeasureSpec The width requirements for this view + * @param parentHeightMeasureSpec The height requirements for this view + */ + protected void measureChild(View child, int parentWidthMeasureSpec, + int parentHeightMeasureSpec) { + final LayoutParams lp = child.getLayoutParams(); + + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + mPaddingLeft + mPaddingRight, lp.width); + final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + mPaddingTop + mPaddingBottom, lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + /** + * Ask one of the children of this view to measure itself, taking into + * account both the MeasureSpec requirements for this view and its padding + * and margins. The child must have MarginLayoutParams The heavy lifting is + * done in getChildMeasureSpec. + * + * @param child The child to measure + * @param parentWidthMeasureSpec The width requirements for this view + * @param widthUsed Extra space that has been used up by the parent + * horizontally (possibly by other children of the parent) + * @param parentHeightMeasureSpec The height requirements for this view + * @param heightUsed Extra space that has been used up by the parent + * vertically (possibly by other children of the parent) + */ + protected void measureChildWithMargins(View child, + int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, + mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + + widthUsed, lp.width); + final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + /** + * Does the hard part of measureChildren: figuring out the MeasureSpec to + * pass to a particular child. This method figures out the right MeasureSpec + * for one dimension (height or width) of one child view. + * + * The goal is to combine information from our MeasureSpec with the + * LayoutParams of the child to get the best possible results. For example, + * if the this view knows its size (because its MeasureSpec has a mode of + * EXACTLY), and the child has indicated in its LayoutParams that it wants + * to be the same size as the parent, the parent should ask the child to + * layout given an exact size. + * + * @param spec The requirements for this view + * @param padding The padding of this view for the current dimension and + * margins, if applicable + * @param childDimension How big the child wants to be in the current + * dimension + * @return a MeasureSpec integer for the child + */ + public static int getChildMeasureSpec(int spec, int padding, int childDimension) { + int specMode = MeasureSpec.getMode(spec); + int specSize = MeasureSpec.getSize(spec); + + int size = Math.max(0, specSize - padding); + + int resultSize = 0; + int resultMode = 0; + + switch (specMode) { + // Parent has imposed an exact size on us + case MeasureSpec.EXACTLY: + if (childDimension >= 0) { + resultSize = childDimension; + resultMode = MeasureSpec.EXACTLY; + } else if (childDimension == LayoutParams.FILL_PARENT) { + // Child wants to be our size. So be it. + resultSize = size; + resultMode = MeasureSpec.EXACTLY; + } else if (childDimension == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size. It can't be + // bigger than us. + resultSize = size; + resultMode = MeasureSpec.AT_MOST; + } + break; + + // Parent has imposed a maximum size on us + case MeasureSpec.AT_MOST: + if (childDimension >= 0) { + // Child wants a specific size... so be it + resultSize = childDimension; + resultMode = MeasureSpec.EXACTLY; + } else if (childDimension == LayoutParams.FILL_PARENT) { + // Child wants to be our size, but our size is not fixed. + // Constrain child to not be bigger than us. + resultSize = size; + resultMode = MeasureSpec.AT_MOST; + } else if (childDimension == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size. It can't be + // bigger than us. + resultSize = size; + resultMode = MeasureSpec.AT_MOST; + } + break; + + // Parent asked to see how big we want to be + case MeasureSpec.UNSPECIFIED: + if (childDimension >= 0) { + // Child wants a specific size... let him have it + resultSize = childDimension; + resultMode = MeasureSpec.EXACTLY; + } else if (childDimension == LayoutParams.FILL_PARENT) { + // Child wants to be our size... find out how big it should + // be + resultSize = 0; + resultMode = MeasureSpec.UNSPECIFIED; + } else if (childDimension == LayoutParams.WRAP_CONTENT) { + // Child wants to determine its own size.... find out how + // big it should be + resultSize = 0; + resultMode = MeasureSpec.UNSPECIFIED; + } + break; + } + return MeasureSpec.makeMeasureSpec(resultSize, resultMode); + } + + + /** + * Removes any pending animations for views that have been removed. Call + * this if you don't want animations for exiting views to stack up. + */ + public void clearDisappearingChildren() { + if (mDisappearingChildren != null) { + mDisappearingChildren.clear(); + } + } + + /** + * Add a view which is removed from mChildren but still needs animation + * + * @param v View to add + */ + private void addDisappearingView(View v) { + ArrayList<View> disappearingChildren = mDisappearingChildren; + + if (disappearingChildren == null) { + disappearingChildren = mDisappearingChildren = new ArrayList<View>(); + } + + disappearingChildren.add(v); + } + + /** + * Cleanup a view when its animation is done. This may mean removing it from + * the list of disappearing views. + * + * @param view The view whose animation has finished + * @param animation The animation, cannot be null + */ + private void finishAnimatingView(final View view, Animation animation) { + final ArrayList<View> disappearingChildren = mDisappearingChildren; + if (disappearingChildren != null) { + if (disappearingChildren.contains(view)) { + disappearingChildren.remove(view); + + if (mAttachInfo != null) { + view.dispatchDetachedFromWindow(); + } + + view.clearAnimation(); + mGroupFlags |= FLAG_INVALIDATE_REQUIRED; + } + } + + if (animation != null && !animation.getFillAfter()) { + view.clearAnimation(); + } + + if ((view.mPrivateFlags & ANIMATION_STARTED) == ANIMATION_STARTED) { + view.onAnimationEnd(); + // Should be performed by onAnimationEnd() but this avoid an infinite loop, + // so we'd rather be safe than sorry + view.mPrivateFlags &= ~ANIMATION_STARTED; + // Draw one more frame after the animation is done + mGroupFlags |= FLAG_INVALIDATE_REQUIRED; + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean gatherTransparentRegion(Region region) { + // If no transparent regions requested, we are always opaque. + final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0; + if (meOpaque && region == null) { + // The caller doesn't care about the region, so stop now. + return true; + } + super.gatherTransparentRegion(region); + final View[] children = mChildren; + final int count = mChildrenCount; + boolean noneOfTheChildrenAreTransparent = true; + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) != GONE || child.getAnimation() != null) { + if (!child.gatherTransparentRegion(region)) { + noneOfTheChildrenAreTransparent = false; + } + } + } + return meOpaque || noneOfTheChildrenAreTransparent; + } + + /** + * {@inheritDoc} + */ + public void requestTransparentRegion(View child) { + if (child != null) { + child.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS; + if (mParent != null) { + mParent.requestTransparentRegion(this); + } + } + } + + + @Override + protected boolean fitSystemWindows(Rect insets) { + boolean done = super.fitSystemWindows(insets); + if (!done) { + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + done = children[i].fitSystemWindows(insets); + if (done) { + break; + } + } + } + return done; + } + + /** + * Returns the animation listener to which layout animation events are + * sent. + * + * @return an {@link android.view.animation.Animation.AnimationListener} + */ + public Animation.AnimationListener getLayoutAnimationListener() { + return mAnimationListener; + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + if ((mGroupFlags & FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE) != 0) { + if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) { + throw new IllegalStateException("addStateFromChildren cannot be enabled if a" + + " child has duplicateParentState set to true"); + } + + final View[] children = mChildren; + final int count = mChildrenCount; + + for (int i = 0; i < count; i++) { + final View child = children[i]; + if ((child.mViewFlags & DUPLICATE_PARENT_STATE) != 0) { + child.refreshDrawableState(); + } + } + } + } + + @Override + protected int[] onCreateDrawableState(int extraSpace) { + if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) == 0) { + return super.onCreateDrawableState(extraSpace); + } + + int need = 0; + int n = getChildCount(); + for (int i = 0; i < n; i++) { + int[] childState = getChildAt(i).getDrawableState(); + + if (childState != null) { + need += childState.length; + } + } + + int[] state = super.onCreateDrawableState(extraSpace + need); + + for (int i = 0; i < n; i++) { + int[] childState = getChildAt(i).getDrawableState(); + + if (childState != null) { + state = mergeDrawableStates(state, childState); + } + } + + return state; + } + + /** + * Sets whether this ViewGroup's drawable states also include + * its children's drawable states. This is used, for example, to + * make a group appear to be focused when its child EditText or button + * is focused. + */ + public void setAddStatesFromChildren(boolean addsStates) { + if (addsStates) { + mGroupFlags |= FLAG_ADD_STATES_FROM_CHILDREN; + } else { + mGroupFlags &= ~FLAG_ADD_STATES_FROM_CHILDREN; + } + + refreshDrawableState(); + } + + /** + * Returns whether this ViewGroup's drawable states also include + * its children's drawable states. This is used, for example, to + * make a group appear to be focused when its child EditText or button + * is focused. + */ + public boolean addStatesFromChildren() { + return (mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0; + } + + /** + * If {link #addStatesFromChildren} is true, refreshes this group's + * drawable state (to include the states from its children). + */ + public void childDrawableStateChanged(View child) { + if ((mGroupFlags & FLAG_ADD_STATES_FROM_CHILDREN) != 0) { + refreshDrawableState(); + } + } + + /** + * Specifies the animation listener to which layout animation events must + * be sent. Only + * {@link android.view.animation.Animation.AnimationListener#onAnimationStart(Animation)} + * and + * {@link android.view.animation.Animation.AnimationListener#onAnimationEnd(Animation)} + * are invoked. + * + * @param animationListener the layout animation listener + */ + public void setLayoutAnimationListener(Animation.AnimationListener animationListener) { + mAnimationListener = animationListener; + } + + /** + * LayoutParams are used by views to tell their parents how they want to be + * laid out. See + * {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes} + * for a list of all child view attributes that this class supports. + * + * <p> + * The base LayoutParams class just describes how big the view wants to be + * for both width and height. For each dimension, it can specify one of: + * <ul> + * <li> an exact number + * <li>FILL_PARENT, which means the view wants to be as big as its parent + * (minus padding) + * <li> WRAP_CONTENT, which means that the view wants to be just big enough + * to enclose its content (plus padding) + * </ul> + * There are subclasses of LayoutParams for different subclasses of + * ViewGroup. For example, AbsoluteLayout has its own subclass of + * LayoutParams which adds an X and Y value. + * + * @attr ref android.R.styleable#ViewGroup_Layout_layout_height + * @attr ref android.R.styleable#ViewGroup_Layout_layout_width + */ + public static class LayoutParams { + /** + * Special value for the height or width requested by a View. + * FILL_PARENT means that the view wants to fill the available space + * within the parent, taking the parent's padding into account. + */ + public static final int FILL_PARENT = -1; + + /** + * Special value for the height or width requested by a View. + * WRAP_CONTENT means that the view wants to be just large enough to fit + * its own internal content, taking its own padding into account. + */ + public static final int WRAP_CONTENT = -2; + + /** + * Information about how wide the view wants to be. Can be an exact + * size, or one of the constants FILL_PARENT or WRAP_CONTENT. + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"), + @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") + }) + public int width; + + /** + * Information about how tall the view wants to be. Can be an exact + * size, or one of the constants FILL_PARENT or WRAP_CONTENT. + */ + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"), + @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") + }) + public int height; + + /** + * Used to animate layouts. + */ + public LayoutAnimationController.AnimationParameters layoutAnimationParameters; + + /** + * Creates a new set of layout parameters. The values are extracted from + * the supplied attributes set and context. The XML attributes mapped + * to this set of layout parameters are: + * + * <ul> + * <li><code>layout_width</code>: the width, either an exact value, + * {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li> + * <li><code>layout_height</code>: the height, either an exact value, + * {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li> + * </ul> + * + * @param c the application environment + * @param attrs the set of attributes from which to extract the layout + * parameters' values + */ + public LayoutParams(Context c, AttributeSet attrs) { + TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout); + setBaseAttributes(a, + R.styleable.ViewGroup_Layout_layout_width, + R.styleable.ViewGroup_Layout_layout_height); + a.recycle(); + } + + /** + * Creates a new set of layout parameters with the specified width + * and height. + * + * @param width the width, either {@link #FILL_PARENT}, + * {@link #WRAP_CONTENT} or a fixed size in pixels + * @param height the height, either {@link #FILL_PARENT}, + * {@link #WRAP_CONTENT} or a fixed size in pixels + */ + public LayoutParams(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * Copy constructor. Clones the width and height values of the source. + * + * @param source The layout params to copy from. + */ + public LayoutParams(LayoutParams source) { + this.width = source.width; + this.height = source.height; + } + + /** + * Used internally by MarginLayoutParams. + * @hide + */ + LayoutParams() { + } + + /** + * Extracts the layout parameters from the supplied attributes. + * + * @param a the style attributes to extract the parameters from + * @param widthAttr the identifier of the width attribute + * @param heightAttr the identifier of the height attribute + */ + protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { + width = a.getLayoutDimension(widthAttr, "layout_width"); + height = a.getLayoutDimension(heightAttr, "layout_height"); + } + + /** + * Returns a String representation of this set of layout parameters. + * + * @param output the String to prepend to the internal representation + * @return a String with the following format: output + + * "ViewGroup.LayoutParams={ width=WIDTH, height=HEIGHT }" + * + * @hide + */ + public String debug(String output) { + return output + "ViewGroup.LayoutParams={ width=" + + sizeToString(width) + ", height=" + sizeToString(height) + " }"; + } + + /** + * Converts the specified size to a readable String. + * + * @param size the size to convert + * @return a String instance representing the supplied size + * + * @hide + */ + protected static String sizeToString(int size) { + if (size == WRAP_CONTENT) { + return "wrap-content"; + } + if (size == FILL_PARENT) { + return "fill-parent"; + } + return String.valueOf(size); + } + } + + /** + * Per-child layout information for layouts that support margins. + * See + * {@link android.R.styleable#ViewGroup_MarginLayout ViewGroup Margin Layout Attributes} + * for a list of all child view attributes that this class supports. + */ + public static class MarginLayoutParams extends ViewGroup.LayoutParams { + /** + * The left margin in pixels of the child. + */ + @ViewDebug.ExportedProperty + public int leftMargin; + + /** + * The top margin in pixels of the child. + */ + @ViewDebug.ExportedProperty + public int topMargin; + + /** + * The right margin in pixels of the child. + */ + @ViewDebug.ExportedProperty + public int rightMargin; + + /** + * The bottom margin in pixels of the child. + */ + @ViewDebug.ExportedProperty + public int bottomMargin; + + /** + * Creates a new set of layout parameters. The values are extracted from + * the supplied attributes set and context. + * + * @param c the application environment + * @param attrs the set of attributes from which to extract the layout + * parameters' values + */ + public MarginLayoutParams(Context c, AttributeSet attrs) { + super(); + + TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); + setBaseAttributes(a, + R.styleable.ViewGroup_MarginLayout_layout_width, + R.styleable.ViewGroup_MarginLayout_layout_height); + + int margin = a.getDimensionPixelSize( + com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1); + if (margin >= 0) { + leftMargin = margin; + topMargin = margin; + rightMargin= margin; + bottomMargin = margin; + } else { + leftMargin = a.getDimensionPixelSize( + R.styleable.ViewGroup_MarginLayout_layout_marginLeft, 0); + topMargin = a.getDimensionPixelSize( + R.styleable.ViewGroup_MarginLayout_layout_marginTop, 0); + rightMargin = a.getDimensionPixelSize( + R.styleable.ViewGroup_MarginLayout_layout_marginRight, 0); + bottomMargin = a.getDimensionPixelSize( + R.styleable.ViewGroup_MarginLayout_layout_marginBottom, 0); + } + + a.recycle(); + } + + /** + * {@inheritDoc} + */ + public MarginLayoutParams(int width, int height) { + super(width, height); + } + + /** + * Copy constructor. Clones the width, height and margin values of the source. + * + * @param source The layout params to copy from. + */ + public MarginLayoutParams(MarginLayoutParams source) { + this.width = source.width; + this.height = source.height; + + this.leftMargin = source.leftMargin; + this.topMargin = source.topMargin; + this.rightMargin = source.rightMargin; + this.bottomMargin = source.bottomMargin; + } + + /** + * {@inheritDoc} + */ + public MarginLayoutParams(LayoutParams source) { + super(source); + } + + /** + * Sets the margins, in pixels. + * + * @param left the left margin size + * @param top the top margin size + * @param right the right margin size + * @param bottom the bottom margin size + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom + */ + public void setMargins(int left, int top, int right, int bottom) { + leftMargin = left; + topMargin = top; + rightMargin = right; + bottomMargin = bottom; + } + } +} diff --git a/core/java/android/view/ViewManager.java b/core/java/android/view/ViewManager.java new file mode 100644 index 0000000..7f318c1 --- /dev/null +++ b/core/java/android/view/ViewManager.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2006 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.view; + +/** Interface to let you add and remove child views to an Activity. To get an instance + * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. + */ +public interface ViewManager +{ + public void addView(View view, ViewGroup.LayoutParams params); + public void updateViewLayout(View view, ViewGroup.LayoutParams params); + public void removeView(View view); +} diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java new file mode 100644 index 0000000..1a5d495 --- /dev/null +++ b/core/java/android/view/ViewParent.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2006 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.view; + +import android.graphics.Rect; + +/** + * Defines the responsibilities for a class that will be a parent of a View. + * This is the API that a view sees when it wants to interact with its parent. + * + */ +public interface ViewParent { + /** + * Called when something has changed which has invalidated the layout of a + * child of this view parent. This will schedule a layout pass of the view + * tree. + */ + public void requestLayout(); + + /** + * Indicates whether layout was requested on this view parent. + * + * @return true if layout was requested, false otherwise + */ + public boolean isLayoutRequested(); + + /** + * Called when a child wants the view hierarchy to gather and report + * transparent regions to the window compositor. Views that "punch" holes in + * the view hierarchy, such as SurfaceView can use this API to improve + * performance of the system. When no such a view is present in the + * hierarchy, this optimization in unnecessary and might slightly reduce the + * view hierarchy performance. + * + * @param child the view requesting the transparent region computation + * + */ + public void requestTransparentRegion(View child); + + /** + * All or part of a child is dirty and needs to be redrawn. + * + * @param child The child which is dirty + * @param r The area within the child that is invalid + */ + public void invalidateChild(View child, Rect r); + + /** + * All or part of a child is dirty and needs to be redrawn. + * + * The location array is an array of two int values which respectively + * define the left and the top position of the dirty child. + * + * This method must return the parent of this ViewParent if the specified + * rectangle must be invalidated in the parent. If the specified rectangle + * does not require invalidation in the parent or if the parent does not + * exist, this method must return null. + * + * When this method returns a non-null value, the location array must + * have been updated with the left and top coordinates of this ViewParent. + * + * @param location An array of 2 ints containing the left and top + * coordinates of the child to invalidate + * @param r The area within the child that is invalid + * + * @return the parent of this ViewParent or null + */ + public ViewParent invalidateChildInParent(int[] location, Rect r); + + /** + * Returns the parent if it exists, or null. + * + * @return a ViewParent or null if this ViewParent does not have a parent + */ + public ViewParent getParent(); + + /** + * Called when a child of this parent wants focus + * + * @param child The child of this ViewParent that wants focus. This view + * will contain the focused view. It is not necessarily the view that + * actually has focus. + * @param focused The view that is a descendant of child that actually has + * focus + */ + public void requestChildFocus(View child, View focused); + + /** + * Tell view hierarchy that the global view attributes need to be + * re-evaluated. + * + * @param child View whose attributes have changed. + */ + public void recomputeViewAttributes(View child); + + /** + * Called when a child of this parent is giving up focus + * + * @param child The view that is giving up focus + */ + public void clearChildFocus(View child); + + public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset); + + /** + * Find the nearest view in the specified direction that wants to take focus + * + * @param v The view that currently has focus + * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT + */ + public View focusSearch(View v, int direction); + + /** + * Change the z order of the child so it's on top of all other children + * + * @param child + */ + public void bringChildToFront(View child); + + /** + * Tells the parent that a new focusable view has become available. This is + * to handle transitions from the case where there are no focusable views to + * the case where the first focusable view appears. + * + * @param v The view that has become newly focusable + */ + public void focusableViewAvailable(View v); + + /** + * Bring up a context menu for the specified view or its ancestors. + * <p> + * In most cases, a subclass does not need to override this. However, if + * the subclass is added directly to the window manager (for example, + * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)}) + * then it should override this and show the context menu. + * + * @param originalView The source view where the context menu was first invoked + * @return true if a context menu was displayed + */ + public boolean showContextMenuForChild(View originalView); + + /** + * Have the parent populate the specified context menu if it has anything to + * add (and then recurse on its parent). + * + * @param menu The menu to populate + */ + public void createContextMenu(ContextMenu menu); + + /** + * This method is called on the parent when a child's drawable state + * has changed. + * + * @param child The child whose drawable state has changed. + */ + public void childDrawableStateChanged(View child); + + /** + * Called when a child does not want this parent and its ancestors to + * intercept touch events with + * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. + * <p> + * This parent should pass this call onto its parents. This parent must obey + * this request for the duration of the touch (that is, only clear the flag + * after this parent has received an up or a cancel. + * + * @param disallowIntercept True if the child does not want the parent to + * intercept touch events. + */ + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept); +} diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java new file mode 100644 index 0000000..ca67404 --- /dev/null +++ b/core/java/android/view/ViewRoot.java @@ -0,0 +1,2212 @@ +/* + * Copyright (C) 2006 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.view; + +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.*; +import android.os.Process; +import android.util.AndroidRuntimeException; +import android.util.Config; +import android.util.Log; +import android.util.EventLog; +import android.view.View.MeasureSpec; +import android.content.pm.PackageManager; +import android.content.Context; +import android.app.ActivityManagerNative; +import android.Manifest; +import android.media.AudioManager; + +import java.lang.ref.WeakReference; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; + +import javax.microedition.khronos.egl.*; +import javax.microedition.khronos.opengles.*; +import static javax.microedition.khronos.opengles.GL10.*; + +/** + * The top of a view hierarchy, implementing the needed protocol between View + * and the WindowManager. This is for the most part an internal implementation + * detail of {@link WindowManagerImpl}. + * + * {@hide} + */ +@SuppressWarnings({"EmptyCatchBlock"}) +final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.SoundEffectPlayer { + private static final String TAG = "ViewRoot"; + private static final boolean DBG = false; + @SuppressWarnings({"ConstantConditionalExpression"}) + private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV; + /** @noinspection PointlessBooleanExpression*/ + private static final boolean DEBUG_DRAW = false || LOCAL_LOGV; + private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV; + private static final boolean DEBUG_TRACKBALL = LOCAL_LOGV; + private static final boolean WATCH_POINTER = false; + + static final boolean PROFILE_DRAWING = false; + private static final boolean PROFILE_LAYOUT = false; + // profiles real fps (times between draws) and displays the result + private static final boolean SHOW_FPS = false; + // used by SHOW_FPS + private static int sDrawTime; + + /** + * Maximum time we allow the user to roll the trackball enough to generate + * a key event, before resetting the counters. + */ + static final int MAX_TRACKBALL_DELAY = 250; + + private static long sInstanceCount = 0; + + private static IWindowSession sWindowSession; + + private static final Object mStaticInit = new Object(); + private static boolean mInitialized = false; + + static final ThreadLocal<Handler> sUiThreads = new ThreadLocal<Handler>(); + static final RunQueue sRunQueue = new RunQueue(); + + private long mLastTrackballTime = 0; + private final TrackballAxis mTrackballAxisX = new TrackballAxis(); + private final TrackballAxis mTrackballAxisY = new TrackballAxis(); + + private final Thread mThread; + + private final WindowLeaked mLocation; + + private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); + + final W mWindow; + + private View mView; + private View mFocusedView; + private int mViewVisibility; + private boolean mAppVisible = true; + + private final Region mTransparentRegion; + private final Region mPreviousTransparentRegion; + + private int mWidth; + private int mHeight; + private Rect mDirty; // will be a graphics.Region soon + + private final View.AttachInfo mAttachInfo; + + private final Rect mTempRect; // used in the transaction to not thrash the heap. + + private boolean mTraversalScheduled; + private boolean mWillDrawSoon; + private boolean mLayoutRequested; + private boolean mFirst; + private boolean mReportNextDraw; + private boolean mFullRedrawNeeded; + private boolean mNewSurfaceNeeded; + + private boolean mWindowAttributesChanged = false; + + // These can be accessed by any thread, must be protected with a lock. + private Surface mSurface; + + private boolean mAdded; + private boolean mAddedTouchMode; + + /*package*/ int mAddNesting; + + // These are accessed by multiple threads. + private final Rect mWinFrame; // frame given by window manager. + + private final Rect mCoveredInsets = new Rect(); + private final Rect mNewCoveredInsets = new Rect(); + + private EGL10 mEgl; + private EGLDisplay mEglDisplay; + private EGLContext mEglContext; + private EGLSurface mEglSurface; + private GL11 mGL; + private Canvas mGlCanvas; + private boolean mUseGL; + private boolean mGlWanted; + + /** + * see {@link #playSoundEffect(int)} + */ + private AudioManager mAudioManager; + + + + public ViewRoot() { + super(); + + ++sInstanceCount; + + // Initialize the statics when this class is first instantiated. This is + // done here instead of in the static block because Zygote does not + // allow the spawning of threads. + synchronized (mStaticInit) { + if (!mInitialized) { + try { + sWindowSession = IWindowManager.Stub.asInterface( + ServiceManager.getService("window")) + .openSession(new Binder()); + mInitialized = true; + } catch (RemoteException e) { + } + } + } + + mThread = Thread.currentThread(); + mLocation = new WindowLeaked(null); + mLocation.fillInStackTrace(); + mWidth = -1; + mHeight = -1; + mDirty = new Rect(); + mTempRect = new Rect(); + mWinFrame = new Rect(); + mWindow = new W(this); + mViewVisibility = View.GONE; + mTransparentRegion = new Region(); + mPreviousTransparentRegion = new Region(); + mFirst = true; // true for the first time the view is added + mSurface = new Surface(); + mAdded = false; + + Handler handler = sUiThreads.get(); + if (handler == null) { + handler = new RootHandler(); + sUiThreads.set(handler); + } + mAttachInfo = new View.AttachInfo(handler, this); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + --sInstanceCount; + } + + public static long getInstanceCount() { + return sInstanceCount; + } + + // FIXME for perf testing only + private boolean mProfile = false; + + /** + * Call this to profile the next traversal call. + * FIXME for perf testing only. Remove eventually + */ + public void profile() { + mProfile = true; + } + + /** + * Indicates whether we are in touch mode. Calling this method triggers an IPC + * call and should be avoided whenever possible. + * + * @return True, if the device is in touch mode, false otherwise. + * + * @hide + */ + static boolean isInTouchMode() { + if (mInitialized) { + try { + return sWindowSession.getInTouchMode(); + } catch (RemoteException e) { + } + } + return false; + } + + private void initializeGL() { + initializeGLInner(); + int err = mEgl.eglGetError(); + if (err != EGL10.EGL_SUCCESS) { + // give-up on using GL + destroyGL(); + mGlWanted = false; + } + } + + private void initializeGLInner() { + final EGL10 egl = (EGL10) EGLContext.getEGL(); + mEgl = egl; + + /* + * Get to the default display. + */ + final EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + mEglDisplay = eglDisplay; + + /* + * We can now initialize EGL for that display + */ + int[] version = new int[2]; + egl.eglInitialize(eglDisplay, version); + + /* + * Specify a configuration for our opengl session + * and grab the first configuration that matches is + */ + final int[] configSpec = { + EGL10.EGL_RED_SIZE, 5, + EGL10.EGL_GREEN_SIZE, 6, + EGL10.EGL_BLUE_SIZE, 5, + EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_NONE + }; + final EGLConfig[] configs = new EGLConfig[1]; + final int[] num_config = new int[1]; + egl.eglChooseConfig(eglDisplay, configSpec, configs, 1, num_config); + final EGLConfig config = configs[0]; + + /* + * Create an OpenGL ES context. This must be done only once, an + * OpenGL context is a somewhat heavy object. + */ + final EGLContext context = egl.eglCreateContext(eglDisplay, config, + EGL10.EGL_NO_CONTEXT, null); + mEglContext = context; + + /* + * Create an EGL surface we can render into. + */ + final EGLSurface surface = egl.eglCreateWindowSurface(eglDisplay, config, mHolder, null); + mEglSurface = surface; + + /* + * Before we can issue GL commands, we need to make sure + * the context is current and bound to a surface. + */ + egl.eglMakeCurrent(eglDisplay, surface, surface, context); + + /* + * Get to the appropriate GL interface. + * This is simply done by casting the GL context to either + * GL10 or GL11. + */ + final GL11 gl = (GL11) context.getGL(); + mGL = gl; + mGlCanvas = new Canvas(gl); + mUseGL = true; + } + + private void destroyGL() { + // inform skia that the context is gone + nativeAbandonGlCaches(); + + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + mEgl.eglTerminate(mEglDisplay); + mEglContext = null; + mEglSurface = null; + mEglDisplay = null; + mEgl = null; + mGlCanvas = null; + mGL = null; + mUseGL = false; + } + + private void checkEglErrors() { + if (mUseGL) { + int err = mEgl.eglGetError(); + if (err != EGL10.EGL_SUCCESS) { + // something bad has happened revert to + // normal rendering. + destroyGL(); + if (err != EGL11.EGL_CONTEXT_LOST) { + // we'll try again if it was context lost + mGlWanted = false; + } + } + } + } + + /** + * We have one child + */ + public void setView(View view, WindowManager.LayoutParams attrs, + View panelParentView) { + synchronized (this) { + if (mView == null) { + mWindowAttributes.copyFrom(attrs); + mWindowAttributesChanged = true; + mView = view; + if (panelParentView != null) { + mAttachInfo.mPanelParentWindowToken + = panelParentView.getApplicationWindowToken(); + } + mAdded = true; + int res; /* = WindowManagerImpl.ADD_OKAY; */ + + // Schedule the first layout -before- adding to the window + // manager, to make sure we do the relayout before receiving + // any other events from the system. + requestLayout(); + + try { + res = sWindowSession.add(mWindow, attrs, + getHostVisibility(), mCoveredInsets); + } catch (RemoteException e) { + mAdded = false; + mView = null; + unscheduleTraversals(); + throw new RuntimeException("Adding window failed", e); + } + if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow); + if (res < WindowManagerImpl.ADD_OKAY) { + mView = null; + mAdded = false; + unscheduleTraversals(); + switch (res) { + case WindowManagerImpl.ADD_BAD_APP_TOKEN: + case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN: + throw new WindowManagerImpl.BadTokenException( + "Unable to add window -- token " + attrs.token + + " is not valid; is your activity running?"); + case WindowManagerImpl.ADD_NOT_APP_TOKEN: + throw new WindowManagerImpl.BadTokenException( + "Unable to add window -- token " + attrs.token + + " is not for an application"); + case WindowManagerImpl.ADD_APP_EXITING: + throw new WindowManagerImpl.BadTokenException( + "Unable to add window -- app for token " + attrs.token + + " is exiting"); + case WindowManagerImpl.ADD_DUPLICATE_ADD: + throw new WindowManagerImpl.BadTokenException( + "Unable to add window -- window " + mWindow + + " has already been added"); + case WindowManagerImpl.ADD_STARTING_NOT_NEEDED: + // Silently ignore -- we would have just removed it + // right away, anyway. + return; + case WindowManagerImpl.ADD_MULTIPLE_SINGLETON: + throw new WindowManagerImpl.BadTokenException( + "Unable to add window " + mWindow + + " -- another window of this type already exists"); + case WindowManagerImpl.ADD_PERMISSION_DENIED: + throw new WindowManagerImpl.BadTokenException( + "Unable to add window " + mWindow + + " -- permission denied for this window type"); + } + throw new RuntimeException( + "Unable to add window -- unknown error code " + res); + } + view.assignParent(this); + mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0; + mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0; + } + } + } + + public View getView() { + return mView; + } + + final WindowLeaked getLocation() { + return mLocation; + } + + public void setLayoutParams(WindowManager.LayoutParams attrs) { + synchronized (this) { + mWindowAttributes.copyFrom(attrs); + mWindowAttributesChanged = true; + scheduleTraversals(); + } + } + + void handleAppVisibility(boolean visible) { + if (mAppVisible != visible) { + mAppVisible = visible; + scheduleTraversals(); + } + } + + void handleGetNewSurface() { + mNewSurfaceNeeded = true; + mFullRedrawNeeded = true; + scheduleTraversals(); + } + + /** + * {@inheritDoc} + */ + public void requestLayout() { + checkThread(); + mLayoutRequested = true; + scheduleTraversals(); + } + + /** + * {@inheritDoc} + */ + public boolean isLayoutRequested() { + return mLayoutRequested; + } + + public void invalidateChild(View child, Rect dirty) { + checkThread(); + if (LOCAL_LOGV) Log.v(TAG, "Invalidate child: " + dirty); + mDirty.union(dirty); + if (!mWillDrawSoon) { + scheduleTraversals(); + } + } + + public ViewParent getParent() { + return null; + } + + public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { + invalidateChild(null, dirty); + return null; + } + + public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { + if (child != mView) { + throw new RuntimeException("child is not mine, honest!"); + } + return r.intersect(0, 0, mWidth, mHeight); + } + + public void bringChildToFront(View child) { + } + + public void scheduleTraversals() { + if (!mTraversalScheduled) { + mTraversalScheduled = true; + sendEmptyMessage(DO_TRAVERSAL); + } + } + + public void unscheduleTraversals() { + if (mTraversalScheduled) { + mTraversalScheduled = false; + removeMessages(DO_TRAVERSAL); + } + } + + int getHostVisibility() { + return mAppVisible ? mView.getVisibility() : View.GONE; + } + + private void performTraversals() { + // cache mView since it is used so much below... + final View host = mView; + + if (DBG) { + System.out.println("======================================"); + System.out.println("performTraversals"); + host.debug(); + } + + if (host == null || !mAdded) + return; + + mTraversalScheduled = false; + mWillDrawSoon = true; + boolean windowResizesToFitContent = false; + boolean fullRedrawNeeded = mFullRedrawNeeded; + boolean newSurface = false; + WindowManager.LayoutParams lp = (WindowManager.LayoutParams) host.getLayoutParams(); + + int desiredWindowWidth; + int desiredWindowHeight; + int childWidthMeasureSpec; + int childHeightMeasureSpec; + + final View.AttachInfo attachInfo = mAttachInfo; + + final int viewVisibility = getHostVisibility(); + boolean viewVisibilityChanged = mViewVisibility != viewVisibility + || mNewSurfaceNeeded; + + WindowManager.LayoutParams params = null; + if (mWindowAttributesChanged) { + mWindowAttributesChanged = false; + params = mWindowAttributes; + } + + if (mFirst) { + fullRedrawNeeded = true; + mLayoutRequested = true; + + Display d = new Display(0); + desiredWindowWidth = d.getWidth(); + desiredWindowHeight = d.getHeight(); + + // For the very first time, tell the view hierarchy that it + // is attached to the window. Note that at this point the surface + // object is not initialized to its backing store, but soon it + // will be (assuming the window is visible). + attachInfo.mWindowToken = mWindow.asBinder(); + attachInfo.mSurface = mSurface; + attachInfo.mSession = sWindowSession; + attachInfo.mHasWindowFocus = false; + attachInfo.mWindowVisibility = viewVisibility; + attachInfo.mRecomputeGlobalAttributes = false; + attachInfo.mKeepScreenOn = false; + viewVisibilityChanged = false; + host.dispatchAttachedToWindow(attachInfo, 0); + sRunQueue.executeActions(attachInfo.mHandler); + //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); + } else { + desiredWindowWidth = mWinFrame.width(); + desiredWindowHeight = mWinFrame.height(); + if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { + if (DEBUG_ORIENTATION) Log.v("ViewRoot", + "View " + host + " resized to: " + mWinFrame); + fullRedrawNeeded = true; + mLayoutRequested = true; + windowResizesToFitContent = true; + } + } + + if (viewVisibilityChanged) { + attachInfo.mWindowVisibility = viewVisibility; + host.dispatchWindowVisibilityChanged(viewVisibility); + if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { + if (mUseGL) { + destroyGL(); + } + } + } + + if (mLayoutRequested) { + if (mFirst) { + host.fitSystemWindows(mCoveredInsets); + // make sure touch mode code executes by setting cached value + // to opposite of the added touch mode. + mAttachInfo.mInTouchMode = !mAddedTouchMode; + ensureTouchModeLocally(mAddedTouchMode); + } else { + if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT + || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + windowResizesToFitContent = true; + + Display d = new Display(0); + desiredWindowWidth = d.getWidth(); + desiredWindowHeight = d.getHeight(); + } + } + + childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); + childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); + + // Ask host how big it wants to be + if (DEBUG_ORIENTATION) Log.v("ViewRoot", + "Measuring " + host + " in display " + desiredWindowWidth + + "x" + desiredWindowHeight + "..."); + host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + + if (DBG) { + System.out.println("======================================"); + System.out.println("performTraversals -- after measure"); + host.debug(); + } + } + + if (attachInfo.mRecomputeGlobalAttributes) { + //Log.i(TAG, "Computing screen on!"); + attachInfo.mRecomputeGlobalAttributes = false; + boolean oldVal = attachInfo.mKeepScreenOn; + attachInfo.mKeepScreenOn = false; + host.dispatchCollectViewAttributes(0); + if (attachInfo.mKeepScreenOn != oldVal) { + params = mWindowAttributes; + //Log.i(TAG, "Keep screen on changed: " + attachInfo.mKeepScreenOn); + } + } + + if (params != null && (host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) { + if (!PixelFormat.formatHasAlpha(params.format)) { + params.format = PixelFormat.TRANSLUCENT; + } + } + + boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent + && (mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight); + + int relayoutResult = 0; + if (mFirst || windowShouldResize || viewVisibilityChanged || params != null) { + + if (viewVisibility == View.VISIBLE) { + if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) { + if (params == null) { + params = mWindowAttributes; + } + mGlWanted = true; + } + } + + final Rect frame = mWinFrame; + boolean initialized = false; + boolean coveredInsetsChanged = false; + try { + boolean hadSurface = mSurface.isValid(); + int fl = 0; + if (params != null) { + fl = params.flags; + if (attachInfo.mKeepScreenOn) { + params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + } + } + relayoutResult = sWindowSession.relayout( + mWindow, params, host.mMeasuredWidth, host.mMeasuredHeight, + viewVisibility, frame, mNewCoveredInsets, mSurface); + if (params != null) { + params.flags = fl; + } + + coveredInsetsChanged = !mNewCoveredInsets.equals(mCoveredInsets); + if (coveredInsetsChanged) { + mCoveredInsets.set(mNewCoveredInsets); + host.fitSystemWindows(mCoveredInsets); + } + + if (!hadSurface && mSurface.isValid()) { + // If we are creating a new surface, then we need to + // completely redraw it. Also, when we get to the + // point of drawing it we will hold off and schedule + // a new traversal instead. This is so we can tell the + // window manager about all of the windows being displayed + // before actually drawing them, so it can display then + // all at once. + newSurface = true; + fullRedrawNeeded = true; + + if (mGlWanted && !mUseGL) { + initializeGL(); + initialized = mGlCanvas != null; + } + } + } catch (RemoteException e) { + } + if (DEBUG_ORIENTATION) Log.v( + "ViewRoot", "Relayout returned: frame=" + mWinFrame + ", surface=" + mSurface); + + attachInfo.mWindowLeft = frame.left; + attachInfo.mWindowTop = frame.top; + + // !!FIXME!! This next section handles the case where we did not get the + // window size we asked for. We should avoid this by getting a maximum size from + // the window session beforehand. + mWidth = frame.width(); + mHeight = frame.height(); + + if (initialized) { + mGlCanvas.setViewport(mWidth, mHeight); + } + + boolean focusChangedDueToTouchMode = ensureTouchModeLocally( + (relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0); + if (focusChangedDueToTouchMode || mWidth != host.mMeasuredWidth + || mHeight != host.mMeasuredHeight || coveredInsetsChanged) { + childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); + childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); + + // Ask host how big it wants to be + host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + + // Implementation of weights from WindowManager.LayoutParams + // We just grow the dimensions as needed and re-measure if + // needs be + int width = host.mMeasuredWidth; + int height = host.mMeasuredHeight; + boolean measureAgain = false; + + if (lp.horizontalWeight > 0.0f) { + width += (int) ((mWidth - width) * lp.horizontalWeight); + childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, + MeasureSpec.EXACTLY); + measureAgain = true; + } + if (lp.verticalWeight > 0.0f) { + height += (int) ((mHeight - height) * lp.verticalWeight); + childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, + MeasureSpec.EXACTLY); + measureAgain = true; + } + + if (measureAgain) { + host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + mLayoutRequested = true; + } + } + + boolean triggerGlobalLayoutListener = mLayoutRequested + || attachInfo.mRecomputeGlobalAttributes; + if (mLayoutRequested) { + mLayoutRequested = false; + if (DEBUG_ORIENTATION) Log.v( + "ViewRoot", "Setting frame " + host + " to (" + + host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")"); + long startTime; + if (PROFILE_LAYOUT) { + startTime = SystemClock.elapsedRealtime(); + } + + host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); + + if (PROFILE_LAYOUT) { + EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime); + } + + // By this point all views have been sized and positionned + // We can compute the transparent area + + if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) { + // start out transparent + // TODO: AVOID THAT CALL BY CACHING THE RESULT? + host.getLocationInWindow(host.mLocation); + mTransparentRegion.set(host.mLocation[0], host.mLocation[1], + host.mLocation[0] + host.mRight - host.mLeft, + host.mLocation[1] + host.mBottom - host.mTop); + + host.gatherTransparentRegion(mTransparentRegion); + if (!mTransparentRegion.equals(mPreviousTransparentRegion)) { + mPreviousTransparentRegion.set(mTransparentRegion); + // reconfigure window manager + try { + sWindowSession.setTransparentRegion(mWindow, mTransparentRegion); + } catch (RemoteException e) { + } + } + } + + + if (DBG) { + System.out.println("======================================"); + System.out.println("performTraversals -- after setFrame"); + host.debug(); + } + } + + if (triggerGlobalLayoutListener) { + attachInfo.mRecomputeGlobalAttributes = false; + attachInfo.mTreeObserver.dispatchOnGlobalLayout(); + } + + if (mFirst) { + // handle first focus request + if (mView != null && !mView.hasFocus()) { + mView.requestFocus(View.FOCUS_FORWARD); + mFocusedView = mView.findFocus(); + } + } + + mFirst = false; + mWillDrawSoon = false; + mNewSurfaceNeeded = false; + mViewVisibility = viewVisibility; + + boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw(); + + if (!cancelDraw && !newSurface) { + mFullRedrawNeeded = false; + draw(fullRedrawNeeded); + + if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0 + || mReportNextDraw) { + if (LOCAL_LOGV) { + Log.v("ViewRoot", "FINISHED DRAWING: " + mWindowAttributes.getTitle()); + } + mReportNextDraw = false; + try { + sWindowSession.finishDrawing(mWindow); + } catch (RemoteException e) { + } + } + } else { + // We were supposed to report when we are done drawing. Since we canceled the + // draw, rememeber it here. + if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { + mReportNextDraw = true; + } + if (fullRedrawNeeded) { + mFullRedrawNeeded = true; + } + // Try again + scheduleTraversals(); + } + } + + public void requestTransparentRegion(View child) { + // the test below should not fail unless someone is messing with us + checkThread(); + if (mView == child) { + mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS; + // Need to make sure we re-evaluate the window attributes next + // time around, to ensure the window has the correct format. + mWindowAttributesChanged = true; + } + } + + /** + * Figures out the measure spec for the root view in a window based on it's + * layout params. + * + * @param windowSize + * The available width or height of the window + * + * @param rootDimension + * The layout params for one dimension (width or height) of the + * window. + * + * @return The measure spec to use to measure the root view. + */ + private int getRootMeasureSpec(int windowSize, int rootDimension) { + int measureSpec; + switch (rootDimension) { + + case ViewGroup.LayoutParams.FILL_PARENT: + // Window can't resize. Force root view to be windowSize. + measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); + break; + case ViewGroup.LayoutParams.WRAP_CONTENT: + // Window can resize. Set max size for root view. + measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); + break; + default: + // Window wants to be an exact size. Force root view to be that size. + measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); + break; + } + return measureSpec; + } + + private void draw(boolean fullRedrawNeeded) { + Surface surface = mSurface; + if (surface == null || !surface.isValid()) { + return; + } + + Rect dirty = mDirty; + if (mUseGL) { + if (!dirty.isEmpty()) { + Canvas canvas = mGlCanvas; + if (mGL!=null && canvas != null) { + mGL.glDisable(GL_SCISSOR_TEST); + mGL.glClearColor(0, 0, 0, 0); + mGL.glClear(GL_COLOR_BUFFER_BIT); + mGL.glEnable(GL_SCISSOR_TEST); + + mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mView.draw(canvas); + + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + checkEglErrors(); + + if (SHOW_FPS) { + int now = (int)SystemClock.elapsedRealtime(); + if (sDrawTime != 0) { + nativeShowFPS(canvas, now - sDrawTime); + } + sDrawTime = now; + } + } + } + return; + } + + + if (fullRedrawNeeded) + dirty.union(0, 0, mWidth, mHeight); + + if (DEBUG_ORIENTATION || DEBUG_DRAW) { + Log.v("ViewRoot", "Draw " + mView + "/" + + mWindowAttributes.getTitle() + + ": dirty={" + dirty.left + "," + dirty.top + + "," + dirty.right + "," + dirty.bottom + "} surface=" + + surface + " surface.isValid()=" + surface.isValid()); + } + + if (!dirty.isEmpty()) { + Canvas canvas; + try { + canvas = surface.lockCanvas(dirty); + } catch (Surface.OutOfResourcesException e) { + Log.e("ViewRoot", "OutOfResourcesException locking surface", e); + // TODO: we should ask the window manager to do something! + // for now we just do nothing + return; + } + + long startTime; + + try { + if (DEBUG_ORIENTATION || DEBUG_DRAW) { + Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w=" + + canvas.getWidth() + ", h=" + canvas.getHeight()); + //canvas.drawARGB(255, 255, 0, 0); + } + + if (PROFILE_DRAWING) { + startTime = SystemClock.elapsedRealtime(); + } + + // If this bitmap's format includes an alpha channel, we + // need to clear it before drawing so that the child will + // properly re-composite its drawing on a transparent + // background. This automatically respects the clip/dirty region + if (!canvas.isOpaque()) { + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + + dirty.setEmpty(); + mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mView.draw(canvas); + + if (SHOW_FPS) { + int now = (int)SystemClock.elapsedRealtime(); + if (sDrawTime != 0) { + nativeShowFPS(canvas, now - sDrawTime); + } + sDrawTime = now; + } + + } finally { + surface.unlockCanvasAndPost(canvas); + } + + if (PROFILE_DRAWING) { + EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + } + + if (LOCAL_LOGV) { + Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost"); + } + } + } + + public void requestChildFocus(View child, View focused) { + checkThread(); + if (mFocusedView != focused) { + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused); + } + mFocusedView = focused; + } + + public void clearChildFocus(View child) { + checkThread(); + + View oldFocus = mFocusedView; + + mFocusedView = null; + if (mView != null && !mView.hasFocus()) { + // If a view gets the focus, the listener will be invoked from requestChildFocus() + if (!mView.requestFocus(View.FOCUS_FORWARD)) { + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); + } + } else if (oldFocus != null) { + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null); + } + } + + + public void focusableViewAvailable(View v) { + checkThread(); + + if (mView != null && !mView.hasFocus()) { + v.requestFocus(); + } else { + // the one case where will transfer focus away from the current one + // is if the current view is a view group that prefers to give focus + // to its children first AND the view is a descendant of it. + mFocusedView = mView.findFocus(); + boolean descendantsHaveDibsOnFocus = + (mFocusedView instanceof ViewGroup) && + (((ViewGroup) mFocusedView).getDescendantFocusability() == + ViewGroup.FOCUS_AFTER_DESCENDANTS); + if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) { + // If a view gets the focus, the listener will be invoked from requestChildFocus() + v.requestFocus(); + } + } + } + + public void recomputeViewAttributes(View child) { + checkThread(); + if (mView == child) { + mAttachInfo.mRecomputeGlobalAttributes = true; + if (!mWillDrawSoon) { + scheduleTraversals(); + } + } + } + + void dispatchDetachedFromWindow() { + if (Config.LOGV) Log.v("ViewRoot", "Detaching in " + this + " of " + mSurface); + if (mView != null) { + mView.dispatchDetachedFromWindow(); + } + mView = null; + if (mUseGL) { + destroyGL(); + } + } + + /** + * Return true if child is an ancestor of parent, (or equal to the parent). + */ + private static boolean isViewDescendantOf(View child, View parent) { + if (child == parent) { + return true; + } + + final ViewParent theParent = child.getParent(); + return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent); + } + + + private final static int DO_TRAVERSAL = 1000; + private final static int DIE = 1001; + private final static int RESIZED = 1002; + private final static int RESIZED_REPORT = 1003; + private final static int WINDOW_FOCUS_CHANGED = 1004; + private final static int DISPATCH_KEY = 1005; + private final static int DISPATCH_POINTER = 1006; + private final static int DISPATCH_TRACKBALL = 1007; + private final static int DISPATCH_APP_VISIBILITY = 1008; + private final static int DISPATCH_GET_NEW_SURFACE = 1009; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case DO_TRAVERSAL: + if (mProfile) { + Debug.startMethodTracing("ViewRoot"); + } + + performTraversals(); + + if (mProfile) { + Debug.stopMethodTracing(); + mProfile = false; + } + break; + case DISPATCH_KEY: + if (LOCAL_LOGV) Log.v( + "ViewRoot", "Dispatching key " + + msg.obj + " to " + mView); + deliverKeyEvent((KeyEvent)msg.obj, true); + break; + case DISPATCH_POINTER: + MotionEvent event = (MotionEvent)msg.obj; + + boolean didFinish; + if (event == null) { + try { + event = sWindowSession.getPendingPointerMove(mWindow); + } catch (RemoteException e) { + } + didFinish = true; + } else { + didFinish = false; + } + + try { + boolean handled; + if (mView != null && mAdded && event != null) { + + // enter touch mode on the down + boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; + if (isDown) { + ensureTouchMode(true); + } + + handled = mView.dispatchTouchEvent(event); + if (!handled && isDown) { + int edgeSlop = ViewConfiguration.getEdgeSlop(); + + final int edgeFlags = event.getEdgeFlags(); + int direction = View.FOCUS_UP; + int x = (int)event.getX(); + int y = (int)event.getY(); + final int[] deltas = new int[2]; + + if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) { + direction = View.FOCUS_DOWN; + if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { + deltas[0] = edgeSlop; + x += edgeSlop; + } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { + deltas[0] = -edgeSlop; + x -= edgeSlop; + } + } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) { + direction = View.FOCUS_UP; + if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { + deltas[0] = edgeSlop; + x += edgeSlop; + } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { + deltas[0] = -edgeSlop; + x -= edgeSlop; + } + } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { + direction = View.FOCUS_RIGHT; + } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { + direction = View.FOCUS_LEFT; + } + + if (edgeFlags != 0 && mView instanceof ViewGroup) { + View nearest = FocusFinder.getInstance().findNearestTouchable( + ((ViewGroup) mView), x, y, direction, deltas); + if (nearest != null) { + event.offsetLocation(deltas[0], deltas[1]); + event.setEdgeFlags(0); + mView.dispatchTouchEvent(event); + } + } + } + } + } finally { + if (!didFinish) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + if (event != null) { + event.recycle(); + } + if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!"); + // Let the exception fall through -- the looper will catch + // it and take care of the bad app for us. + } + break; + case DISPATCH_TRACKBALL: + deliverTrackballEvent((MotionEvent)msg.obj); + break; + case DISPATCH_APP_VISIBILITY: + handleAppVisibility(msg.arg1 != 0); + break; + case DISPATCH_GET_NEW_SURFACE: + handleGetNewSurface(); + break; + case RESIZED: + if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2) { + break; + } + // fall through... + case RESIZED_REPORT: + if (mAdded) { + mWinFrame.left = 0; + mWinFrame.right = msg.arg1; + mWinFrame.top = 0; + mWinFrame.bottom = msg.arg2; + if (msg.what == RESIZED_REPORT) { + mReportNextDraw = true; + } + requestLayout(); + } + break; + case WINDOW_FOCUS_CHANGED: { + if (mAdded) { + boolean hasWindowFocus = msg.arg1 != 0; + mAttachInfo.mHasWindowFocus = hasWindowFocus; + if (hasWindowFocus) { + boolean inTouchMode = msg.arg2 != 0; + ensureTouchModeLocally(inTouchMode); + + if (mGlWanted) { + checkEglErrors(); + // we lost the gl context, so recreate it. + if (mGlWanted && !mUseGL) { + initializeGL(); + if (mGlCanvas != null) { + mGlCanvas.setViewport(mWidth, mHeight); + } + } + } + } + if (mView != null) { + mView.dispatchWindowFocusChanged(hasWindowFocus); + } + } + } break; + case DIE: + dispatchDetachedFromWindow(); + break; + } + } + + /** + * Something in the current window tells us we need to change the touch mode. For + * example, we are not in touch mode, and the user touches the screen. + * + * If the touch mode has changed, tell the window manager, and handle it locally. + * + * @param inTouchMode Whether we want to be in touch mode. + * @return True if the touch mode changed and focus changed was changed as a result + */ + boolean ensureTouchMode(boolean inTouchMode) { + if (DBG) Log.d("touchmode", "ensureTouchMode(" + inTouchMode + "), current " + + "touch mode is " + mAttachInfo.mInTouchMode); + if (mAttachInfo.mInTouchMode == inTouchMode) return false; + + // tell the window manager + try { + sWindowSession.setInTouchMode(inTouchMode); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + // handle the change + return ensureTouchModeLocally(inTouchMode); + } + + /** + * Ensure that the touch mode for this window is set, and if it is changing, + * take the appropriate action. + * @param inTouchMode Whether we want to be in touch mode. + * @return True if the touch mode changed and focus changed was changed as a result + */ + private boolean ensureTouchModeLocally(boolean inTouchMode) { + if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current " + + "touch mode is " + mAttachInfo.mInTouchMode); + + if (mAttachInfo.mInTouchMode == inTouchMode) return false; + + mAttachInfo.mInTouchMode = inTouchMode; + mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode); + + return (inTouchMode) ? enterTouchMode() : leaveTouchMode(); + } + + private boolean enterTouchMode() { + if (mView != null) { + if (mView.hasFocus()) { + // note: not relying on mFocusedView here because this could + // be when the window is first being added, and mFocused isn't + // set yet. + final View focused = mView.findFocus(); + if (focused != null && !focused.isFocusableInTouchMode()) { + + final ViewGroup ancestorToTakeFocus = + findAncestorToTakeFocusInTouchMode(focused); + if (ancestorToTakeFocus != null) { + // there is an ancestor that wants focus after its descendants that + // is focusable in touch mode.. give it focus + return ancestorToTakeFocus.requestFocus(); + } else { + // nothing appropriate to have focus in touch mode, clear it out + mView.unFocus(); + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null); + mFocusedView = null; + return true; + } + } + } + } + return false; + } + + + /** + * Find an ancestor of focused that wants focus after its descendants and is + * focusable in touch mode. + * @param focused The currently focused view. + * @return An appropriate view, or null if no such view exists. + */ + private ViewGroup findAncestorToTakeFocusInTouchMode(View focused) { + ViewParent parent = focused.getParent(); + while (parent instanceof ViewGroup) { + final ViewGroup vgParent = (ViewGroup) parent; + if (vgParent.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS + && vgParent.isFocusableInTouchMode()) { + return vgParent; + } + if (vgParent.isRootNamespace()) { + return null; + } else { + parent = vgParent.getParent(); + } + } + return null; + } + + private boolean leaveTouchMode() { + if (mView != null) { + if (mView.hasFocus()) { + // i learned the hard way to not trust mFocusedView :) + mFocusedView = mView.findFocus(); + if (!(mFocusedView instanceof ViewGroup)) { + // some view has focus, let it keep it + return false; + } else if (((ViewGroup)mFocusedView).getDescendantFocusability() != + ViewGroup.FOCUS_AFTER_DESCENDANTS) { + // some view group has focus, and doesn't prefer its children + // over itself for focus, so let them keep it. + return false; + } + } + + // find the best view to give focus to in this brave new non-touch-mode + // world + final View focused = focusSearch(null, View.FOCUS_DOWN); + if (focused != null) { + return focused.requestFocus(View.FOCUS_DOWN); + } + } + return false; + } + + + private void deliverTrackballEvent(MotionEvent event) { + boolean didFinish; + if (event == null) { + try { + event = sWindowSession.getPendingTrackballMove(mWindow); + } catch (RemoteException e) { + } + didFinish = true; + } else { + didFinish = false; + } + + //Log.i("foo", "Motion event:" + event); + + boolean handled = false; + try { + if (event == null) { + handled = true; + } else if (mView != null && mAdded) { + handled = mView.dispatchTrackballEvent(event); + if (!handled) { + // we could do something here, like changing the focus + // or someting? + } + } + } finally { + if (handled) { + if (!didFinish) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + if (event != null) { + event.recycle(); + } + //noinspection ReturnInsideFinallyBlock + return; + } + // Let the exception fall through -- the looper will catch + // it and take care of the bad app for us. + } + + final TrackballAxis x = mTrackballAxisX; + final TrackballAxis y = mTrackballAxisY; + + long curTime = SystemClock.uptimeMillis(); + if ((mLastTrackballTime+MAX_TRACKBALL_DELAY) < curTime) { + // It has been too long since the last movement, + // so restart at the beginning. + x.reset(0); + y.reset(0); + mLastTrackballTime = curTime; + } + + try { + final int action = event.getAction(); + final int metastate = event.getMetaState(); + switch (action) { + case MotionEvent.ACTION_DOWN: + x.reset(2); + y.reset(2); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, + 0, metastate), false); + break; + case MotionEvent.ACTION_UP: + x.reset(2); + y.reset(2); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, + 0, metastate), false); + break; + } + + if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step=" + + x.step + " dir=" + x.dir + " acc=" + x.acceleration + + " move=" + event.getX() + + " / Y=" + y.position + " step=" + + y.step + " dir=" + y.dir + " acc=" + y.acceleration + + " move=" + event.getY()); + final float xOff = x.collect(event.getX(), "X"); + final float yOff = y.collect(event.getY(), "Y"); + + // Generate DPAD events based on the trackball movement. + // We pick the axis that has moved the most as the direction of + // the DPAD. When we generate DPAD events for one axis, then the + // other axis is reset -- we don't want to perform DPAD jumps due + // to slight movements in the trackball when making major movements + // along the other axis. + int keycode = 0; + int movement = 0; + float accel = 1; + if (xOff > yOff) { + movement = x.generate((2/event.getXPrecision())); + if (movement != 0) { + keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT + : KeyEvent.KEYCODE_DPAD_LEFT; + accel = x.acceleration; + y.reset(2); + } + } else if (yOff > 0) { + movement = y.generate((2/event.getYPrecision())); + if (movement != 0) { + keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN + : KeyEvent.KEYCODE_DPAD_UP; + accel = y.acceleration; + x.reset(2); + } + } + + if (keycode != 0) { + if (movement < 0) movement = -movement; + int accelMovement = (int)(movement * accel); + //Log.i(TAG, "Move: movement=" + movement + // + " accelMovement=" + accelMovement + // + " accel=" + accel); + if (accelMovement > movement) { + if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " + + keycode); + movement--; + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_MULTIPLE, keycode, + accelMovement-movement, metastate), false); + } + while (movement > 0) { + if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " + + keycode); + movement--; + curTime = SystemClock.uptimeMillis(); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_DOWN, keycode, 0, event.getMetaState()), false); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_UP, keycode, 0, metastate), false); + } + mLastTrackballTime = curTime; + } + } finally { + if (!didFinish) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + if (event != null) { + event.recycle(); + } + } + // Let the exception fall through -- the looper will catch + // it and take care of the bad app for us. + } + } + + /** + * @param keyCode The key code + * @return True if the key is directional. + */ + static boolean isDirectional(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + return true; + } + return false; + } + + /** + * Returns true if this key is a keyboard key. + * @param keyEvent The key event. + * @return whether this key is a keyboard key. + */ + private static boolean isKeyboardKey(KeyEvent keyEvent) { + final int convertedKey = keyEvent.getUnicodeChar(); + return convertedKey > 0; + } + + + + /** + * See if the key event means we should leave touch mode (and leave touch + * mode if so). + * @param event The key event. + * @return Whether this key event should be consumed (meaning the act of + * leaving touch mode alone is considered the event). + */ + private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_DOWN) { + return false; + } + + // only relevant if we are in touch mode + if (!mAttachInfo.mInTouchMode) { + return false; + } + + // if something like an edit text has focus and the user is typing, + // leave touch mode + // + // note: the condition of not being a keyboard key is kind of a hacky + // approximation of whether we think the focused view will want the + // key; if we knew for sure whether the focused view would consume + // the event, that would be better. + if (isKeyboardKey(event) && mView != null && mView.hasFocus()) { + mFocusedView = mView.findFocus(); + if ((mFocusedView instanceof ViewGroup) + && ((ViewGroup) mFocusedView).getDescendantFocusability() == + ViewGroup.FOCUS_AFTER_DESCENDANTS) { + // something has focus, but is holding it weakly as a container + return false; + } + if (ensureTouchMode(false)) { + throw new IllegalStateException("should not have changed focus " + + "when leaving touch mode while a view has focus."); + } + return false; + } + + if (isDirectional(event.getKeyCode())) { + // no view has focus, so we leave touch mode (and find something + // to give focus to). the event is consumed if we were able to + // find something to give focus to. + return ensureTouchMode(false); + } + return false; + } + + + private void deliverKeyEvent(KeyEvent event, boolean sendDone) { + try { + if (mView != null && mAdded) { + final int action = event.getAction(); + boolean isDown = (action == KeyEvent.ACTION_DOWN); + + if (checkForLeavingTouchModeAndConsume(event)) { + return; + } + + boolean keyHandled = mView.dispatchKeyEvent(event); + + if ((!keyHandled && isDown) || (action == KeyEvent.ACTION_MULTIPLE)) { + int direction = 0; + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_LEFT: + direction = View.FOCUS_LEFT; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + direction = View.FOCUS_RIGHT; + break; + case KeyEvent.KEYCODE_DPAD_UP: + direction = View.FOCUS_UP; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + direction = View.FOCUS_DOWN; + break; + } + + if (direction != 0) { + + View focused = mView != null ? mView.findFocus() : null; + if (focused != null) { + View v = focused.focusSearch(direction); + boolean focusPassed = false; + if (v != null && v != focused) { + // do the math the get the interesting rect + // of previous focused into the coord system of + // newly focused view + focused.getFocusedRect(mTempRect); + ((ViewGroup) mView).offsetDescendantRectToMyCoords(focused, mTempRect); + ((ViewGroup) mView).offsetRectIntoDescendantCoords(v, mTempRect); + focusPassed = v.requestFocus(direction, mTempRect); + } + + if (!focusPassed) { + mView.dispatchUnhandledMove(focused, direction); + } else { + playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); + } + } + } + } + } + + } finally { + if (sendDone) { + if (LOCAL_LOGV) Log.v( + "ViewRoot", "Telling window manager key is finished"); + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + // Let the exception fall through -- the looper will catch + // it and take care of the bad app for us. + } + } + + private AudioManager getAudioManager() { + if (mView == null) { + throw new IllegalStateException("getAudioManager called when there is no mView"); + } + if (mAudioManager == null) { + mAudioManager = (AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE); + } + return mAudioManager; + } + + /** + * {@inheritDoc} + */ + public void playSoundEffect(int effectId) { + checkThread(); + + final AudioManager audioManager = getAudioManager(); + + switch (effectId) { + case SoundEffectConstants.CLICK: + audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); + return; + case SoundEffectConstants.NAVIGATION_DOWN: + audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN); + return; + case SoundEffectConstants.NAVIGATION_LEFT: + audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT); + return; + case SoundEffectConstants.NAVIGATION_RIGHT: + audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT); + return; + case SoundEffectConstants.NAVIGATION_UP: + audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP); + return; + default: + throw new IllegalArgumentException("unknown effect id " + effectId + + " not defined in " + SoundEffectConstants.class.getCanonicalName()); + } + } + + /** + * {@inheritDoc} + */ + public View focusSearch(View focused, int direction) { + checkThread(); + if (!(mView instanceof ViewGroup)) { + return null; + } + return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction); + } + + public void debug() { + mView.debug(); + } + + public void die(boolean immediate) { + checkThread(); + if (Config.LOGV) Log.v("ViewRoot", "DIE in " + this + " of " + mSurface); + synchronized (this) { + if (mAdded && !mFirst) { + int viewVisibility = mView.getVisibility(); + boolean viewVisibilityChanged = mViewVisibility != viewVisibility; + if (mWindowAttributesChanged || viewVisibilityChanged) { + // If layout params have been changed, first give them + // to the window manager to make sure it has the correct + // animation info. + try { + if ((sWindowSession.relayout( + mWindow, mWindowAttributes, + mView.mMeasuredWidth, mView.mMeasuredHeight, + viewVisibility, mWinFrame, mCoveredInsets, mSurface) + &WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { + sWindowSession.finishDrawing(mWindow); + } + } catch (RemoteException e) { + } + } + + mSurface = null; + } + if (mAdded) { + mAdded = false; + try { + sWindowSession.remove(mWindow); + } catch (RemoteException e) { + } + if (immediate) { + dispatchDetachedFromWindow(); + } else if (mView != null) { + sendEmptyMessage(DIE); + } + } + } + } + + public void dispatchResized(int w, int h, boolean reportDraw) { + if (DEBUG_DRAW) Log.v(TAG, "Resized " + this + ": w=" + w + + " h=" + h + " reportDraw=" + reportDraw); + Message msg = obtainMessage(reportDraw ? RESIZED_REPORT : RESIZED); + msg.arg1 = w; + msg.arg2 = h; + sendMessage(msg); + } + + public void dispatchKey(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + //noinspection ConstantConditions + if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { + if (Config.LOGD) Log.d("keydisp", + "==================================================="); + if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:"); + debug(); + + if (Config.LOGD) Log.d("keydisp", + "==================================================="); + } + } + + Message msg = obtainMessage(DISPATCH_KEY); + msg.obj = event; + + if (LOCAL_LOGV) Log.v( + "ViewRoot", "sending key " + event + " to " + mView); + + sendMessageAtTime(msg, event.getEventTime()); + } + + public void dispatchPointer(MotionEvent event, long eventTime) { + Message msg = obtainMessage(DISPATCH_POINTER); + msg.obj = event; + sendMessageAtTime(msg, eventTime); + } + + public void dispatchTrackball(MotionEvent event, long eventTime) { + Message msg = obtainMessage(DISPATCH_TRACKBALL); + msg.obj = event; + sendMessageAtTime(msg, eventTime); + } + + public void dispatchAppVisibility(boolean visible) { + Message msg = obtainMessage(DISPATCH_APP_VISIBILITY); + msg.arg1 = visible ? 1 : 0; + sendMessage(msg); + } + + public void dispatchGetNewSurface() { + Message msg = obtainMessage(DISPATCH_GET_NEW_SURFACE); + sendMessage(msg); + } + + public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { + Message msg = Message.obtain(); + msg.what = WINDOW_FOCUS_CHANGED; + msg.arg1 = hasFocus ? 1 : 0; + msg.arg2 = inTouchMode ? 1 : 0; + sendMessage(msg); + } + + public boolean showContextMenuForChild(View originalView) { + return false; + } + + public void createContextMenu(ContextMenu menu) { + } + + public void childDrawableStateChanged(View child) { + } + + protected Rect getWindowFrame() { + return mWinFrame; + } + + void checkThread() { + if (mThread != Thread.currentThread()) { + throw new CalledFromWrongThreadException( + "Only the original thread that created a view hierarchy can touch its views."); + } + } + + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + // ViewRoot never intercepts touch event, so this can be a no-op + } + + static class W extends IWindow.Stub { + private WeakReference<ViewRoot> mViewRoot; + + public W(ViewRoot viewRoot) { + mViewRoot = new WeakReference<ViewRoot>(viewRoot); + } + + public void resized(int w, int h, boolean reportDraw) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchResized(w, h, reportDraw); + } + } + + public void dispatchKey(KeyEvent event) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchKey(event); + } + } + + public void dispatchPointer(MotionEvent event, long eventTime) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchPointer(event, eventTime); + } + } + + public void dispatchTrackball(MotionEvent event, long eventTime) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchTrackball(event, eventTime); + } + } + + public void dispatchAppVisibility(boolean visible) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchAppVisibility(visible); + } + } + + public void dispatchGetNewSurface() { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchGetNewSurface(); + } + } + + public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.windowFocusChanged(hasFocus, inTouchMode); + } + } + + private static int checkCallingPermission(String permission) { + if (!Process.supportsProcesses()) { + return PackageManager.PERMISSION_GRANTED; + } + + try { + return ActivityManagerNative.getDefault().checkPermission( + permission, Binder.getCallingPid(), Binder.getCallingUid()); + } catch (RemoteException e) { + return PackageManager.PERMISSION_DENIED; + } + } + + public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + final View view = viewRoot.mView; + if (view != null) { + if (checkCallingPermission(Manifest.permission.DUMP) != + PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Insufficient permissions to invoke" + + " executeCommand() from pid=" + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + } + + OutputStream clientStream = null; + try { + clientStream = new ParcelFileDescriptor.AutoCloseOutputStream(out); + ViewDebug.dispatchCommand(view, command, parameters, clientStream); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (clientStream != null) { + try { + clientStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + } + } + + /** + * Maintains state information for a single trackball axis, generating + * discrete (DPAD) movements based on raw trackball motion. + */ + static final class TrackballAxis { + float position; + float absPosition; + float acceleration = 1; + int step; + int dir; + int nonAccelMovement; + + void reset(int _step) { + position = 0; + acceleration = 1; + step = _step; + dir = 0; + } + + /** + * Add trackball movement into the state. If the direction of movement + * has been reversed, the state is reset before adding the + * movement (so that you don't have to compensate for any previously + * collected movement before see the result of the movement in the + * new direction). + * + * @return Returns the absolute value of the amount of movement + * collected so far. + */ + float collect(float off, String axis) { + if (off > 0) { + if (dir < 0) { + if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!"); + position = 0; + step = 0; + acceleration = 1; + } + dir = 1; + } else if (off < 0) { + if (dir > 0) { + if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!"); + position = 0; + step = 0; + acceleration = 1; + } + dir = -1; + } + position += off; + return (absPosition = Math.abs(position)); + } + + /** + * Generate the number of discrete movement events appropriate for + * the currently collected trackball movement. + * + * @param precision The minimum movement required to generate the + * first discrete movement. + * + * @return Returns the number of discrete movements, either positive + * or negative, or 0 if there is not enough trackball movement yet + * for a discrete movement. + */ + int generate(float precision) { + int movement = 0; + nonAccelMovement = 0; + do { + final int dir = position >= 0 ? 1 : -1; + switch (step) { + // If we are going to execute the first step, then we want + // to do this as soon as possible instead of waiting for + // a full movement, in order to make things look responsive. + case 0: + if (absPosition < precision) { + return movement; + } + movement += dir; + nonAccelMovement += dir; + step = 1; + break; + // If we have generated the first movement, then we need + // to wait for the second complete trackball motion before + // generating the second discrete movement. + case 1: + if (absPosition < 2) { + return movement; + } + movement += dir; + nonAccelMovement += dir; + position += dir > 0 ? -2 : 2; + absPosition = Math.abs(position); + step = 2; + break; + // After the first two, we generate discrete movements + // consistently with the trackball, applying an acceleration + // if the trackball is moving quickly. The acceleration is + // currently very simple, just reducing the amount of + // trackball motion required as more discrete movements are + // generated. This should probably be changed to take time + // more into account, so that quick trackball movements will + // have increased acceleration. + default: + if (absPosition < 1) { + return movement; + } + movement += dir; + position += dir >= 0 ? -1 : 1; + absPosition = Math.abs(position); + float acc = acceleration; + acc *= 1.1f; + acceleration = acc < 20 ? acc : acceleration; + break; + } + } while (true); + } + } + + public static final class CalledFromWrongThreadException extends AndroidRuntimeException { + public CalledFromWrongThreadException(String msg) { + super(msg); + } + } + + private static final class RootHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case View.AttachInfo.INVALIDATE_MSG: + ((View) msg.obj).invalidate(); + break; + case View.AttachInfo.INVALIDATE_RECT_MSG: + int left = msg.arg1 >>> 16; + int top = msg.arg1 & 0xFFFF; + int right = msg.arg2 >>> 16; + int bottom = msg.arg2 & 0xFFFF; + ((View) msg.obj).invalidate(left, top, right, bottom); + break; + } + } + } + + private SurfaceHolder mHolder = new SurfaceHolder() { + // we only need a SurfaceHolder for opengl. it would be nice + // to implement everything else though, especially the callback + // support (opengl doesn't make use of it right now, but eventually + // will). + public Surface getSurface() { + return mSurface; + } + + public boolean isCreating() { + return false; + } + + public void addCallback(Callback callback) { + } + + public void removeCallback(Callback callback) { + } + + public void setFixedSize(int width, int height) { + } + + public void setSizeFromLayout() { + } + + public void setFormat(int format) { + } + + public void setType(int type) { + } + + public void setKeepScreenOn(boolean screenOn) { + } + + public Canvas lockCanvas() { + return null; + } + + public Canvas lockCanvas(Rect dirty) { + return null; + } + + public void unlockCanvasAndPost(Canvas canvas) { + } + public Rect getSurfaceFrame() { + return null; + } + }; + + /** + * @hide + */ + static final class RunQueue { + private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>(); + + void post(Runnable action) { + postDelayed(action, 0); + } + + void postDelayed(Runnable action, long delayMillis) { + HandlerAction handlerAction = new HandlerAction(); + handlerAction.action = action; + handlerAction.delay = delayMillis; + + synchronized (mActions) { + mActions.add(handlerAction); + } + } + + void removeCallbacks(Runnable action) { + final HandlerAction handlerAction = new HandlerAction(); + handlerAction.action = action; + + synchronized (mActions) { + final ArrayList<HandlerAction> actions = mActions; + final int count = actions.size(); + + while (actions.remove(handlerAction)) { + // Keep going + } + } + } + + void executeActions(Handler handler) { + synchronized (mActions) { + final ArrayList<HandlerAction> actions = mActions; + final int count = actions.size(); + + for (int i = 0; i < count; i++) { + final HandlerAction handlerAction = actions.get(i); + handler.postDelayed(handlerAction.action, handlerAction.delay); + } + + mActions.clear(); + } + } + + private static class HandlerAction { + Runnable action; + long delay; + + @Override + public boolean equals(Object o) { + return action.equals(o); + } + } + } + + private static native void nativeShowFPS(Canvas canvas, int durationMillis); + + // inform skia to just abandon its texture cache IDs + // doesn't call glDeleteTextures + private static native void nativeAbandonGlCaches(); +} diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java new file mode 100644 index 0000000..e159de4 --- /dev/null +++ b/core/java/android/view/ViewStub.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2008 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.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.util.AttributeSet; + +import com.android.internal.R; + +/** + * A ViewStub is an invisible, zero-sized View that can be used to lazily inflate + * layout resources at runtime. + * + * When a ViewStub is made visible, or when {@link #inflate()} is invoked, the layout resource + * is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. + * Therefore, the ViewStub exists in the view hierarchy until {@link #setVisibility(int)} or + * {@link #inflate()} is invoked. + * + * The inflated View is added to the ViewStub's parent with the ViewStub's layout + * parameters. Similarly, you can define/override the inflate View's id by using the + * ViewStub's inflatedId property. For instance: + * + * <pre> + * <ViewStub android:id="@+id/stub" + * android:inflatedId="@+id/subTree" + * android:layout="@layout/mySubTree" + * android:layout_width="120dip" + * android:layout_height="40dip" /> + * </pre> + * + * The ViewStub thus defined can be found using the id "stub." After inflation of + * the layout resource "mySubTree," the ViewStub is removed from its parent. The + * View created by inflating the layout resource "mySubTree" can be found using the + * id "subTree," specified by the inflatedId property. The inflated View is finally + * assigned a width of 120dip and a height of 40dip. + * + * The preferred way to perform the inflation of the layout resource is the following: + * + * <pre> + * ViewStub stub = (ViewStub) findViewById(R.id.stub); + * View inflated = stub.inflate(); + * </pre> + * + * When {@link #inflate()} is invoked, the ViewStub is replaced by the inflated View + * and the inflated View is returned. This lets applications get a reference to the + * inflated View without executing an extra findViewById(). + * + * @attr ref android.R.styleable#ViewStub_inflatedId + * @attr ref android.R.styleable#ViewStub_layout + */ +public final class ViewStub extends View { + private int mLayoutResource = 0; + private int mInflatedId; + + private OnInflateListener mInflateListener; + + public ViewStub(Context context) { + initialize(context); + } + + /** + * Creates a new ViewStub with the specified layout resource. + * + * @param context The application's environment. + * @param layoutResource The reference to a layout resource that will be inflated. + */ + public ViewStub(Context context, int layoutResource) { + mLayoutResource = layoutResource; + initialize(context); + } + + public ViewStub(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @SuppressWarnings({"UnusedDeclaration"}) + public ViewStub(Context context, AttributeSet attrs, int defStyle) { + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub, + defStyle, 0); + + mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); + mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); + + a.recycle(); + + a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0); + mID = a.getResourceId(R.styleable.View_id, NO_ID); + a.recycle(); + + initialize(context); + } + + private void initialize(Context context) { + mContext = context; + setVisibility(GONE); + setWillNotDraw(true); + } + + /** + * Returns the id taken by the inflated view. If the inflated id is + * {@link View#NO_ID}, the inflated view keeps its original id. + * + * @return A positive integer used to identify the inflated view or + * {@link #NO_ID} if the inflated view should keep its id. + * + * @see #setInflatedId(int) + * @attr ref android.R.styleable#ViewStub_inflatedId + */ + public int getInflatedId() { + return mInflatedId; + } + + /** + * Defines the id taken by the inflated view. If the inflated id is + * {@link View#NO_ID}, the inflated view keeps its original id. + * + * @param inflatedId A positive integer used to identify the inflated view or + * {@link #NO_ID} if the inflated view should keep its id. + * + * @see #getInflatedId() + * @attr ref android.R.styleable#ViewStub_inflatedId + */ + public void setInflatedId(int inflatedId) { + mInflatedId = inflatedId; + } + + /** + * Returns the layout resource that will be used by {@link #setVisibility(int)} or + * {@link #inflate()} to replace this StubbedView + * in its parent by another view. + * + * @return The layout resource identifier used to inflate the new View. + * + * @see #setLayoutResource(int) + * @see #setVisibility(int) + * @see #inflate() + * @attr ref android.R.styleable#ViewStub_layout + */ + public int getLayoutResource() { + return mLayoutResource; + } + + /** + * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible + * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is + * used to replace this StubbedView in its parent. + * + * @param layoutResource A valid layout resource identifier (different from 0.) + * + * @see #getLayoutResource() + * @see #setVisibility(int) + * @see #inflate() + * @attr ref android.R.styleable#ViewStub_layout + */ + public void setLayoutResource(int layoutResource) { + mLayoutResource = layoutResource; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(0, 0); + } + + @Override + public void draw(Canvas canvas) { + } + + @Override + protected void dispatchDraw(Canvas canvas) { + } + + /** + * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE}, + * {@link #inflate()} is invoked and this StubbedView is replaced in its parent + * by the inflated layout resource. + * + * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. + * + * @see #inflate() + */ + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + + if (visibility == VISIBLE || visibility == INVISIBLE) { + inflate(); + } + } + + /** + * Inflates the layout resource identified by {@link #getLayoutResource()} + * and replaces this StubbedView in its parent by the inflated layout resource. + * + * @return The inflated layout resource. + * + */ + public View inflate() { + final ViewParent viewParent = getParent(); + + if (viewParent != null && viewParent instanceof ViewGroup) { + if (mLayoutResource != 0) { + final ViewGroup parent = (ViewGroup) viewParent; + final LayoutInflater factory = LayoutInflater.from(mContext); + final View view = factory.inflate(mLayoutResource, parent, + false); + + if (mInflatedId != NO_ID) { + view.setId(mInflatedId); + } + + final int index = parent.indexOfChild(this); + parent.removeViewInLayout(this); + + final ViewGroup.LayoutParams layoutParams = getLayoutParams(); + if (layoutParams != null) { + parent.addView(view, index, layoutParams); + } else { + parent.addView(view, index); + } + + if (mInflateListener != null) { + mInflateListener.onInflate(this, view); + } + + return view; + } else { + throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); + } + } else { + throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); + } + } + + /** + * Specifies the inflate listener to be notified after this ViewStub successfully + * inflated its layout resource. + * + * @param inflateListener The OnInflateListener to notify of successful inflation. + * + * @see android.view.ViewStub.OnInflateListener + */ + public void setOnInflateListener(OnInflateListener inflateListener) { + mInflateListener = inflateListener; + } + + /** + * Listener used to receive a notification after a ViewStub has successfully + * inflated its layout resource. + * + * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener) + */ + public static interface OnInflateListener { + /** + * Invoked after a ViewStub successfully inflated its layout resource. + * This method is invoked after the inflated view was added to the + * hierarchy but before the layout pass. + * + * @param stub The ViewStub that initiated the inflation. + * @param inflated The inflated View. + */ + void onInflate(ViewStub stub, View inflated); + } +} diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java new file mode 100644 index 0000000..2e1e01a --- /dev/null +++ b/core/java/android/view/ViewTreeObserver.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2008 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.view; + +import java.util.ArrayList; + +/** + * A view tree observer is used to register listeners that can be notified of global + * changes in the view tree. Such global events include, but are not limited to, + * layout of the whole tree, beginning of the drawing pass, touch mode change.... + * + * A ViewTreeObserver should never be instantiated by applications as it is provided + * by the views hierarchy. Refer to {@link android.view.View#getViewTreeObserver()} + * for more information. + */ +public final class ViewTreeObserver { + private ArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; + private ArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; + private ArrayList<OnPreDrawListener> mOnPreDrawListeners; + private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; + + private boolean mAlive = true; + + /** + * Interface definition for a callback to be invoked when the focus state within + * the view tree changes. + */ + public interface OnGlobalFocusChangeListener { + /** + * Callback method to be invoked when the focus changes in the view tree. When + * the view tree transitions from touch mode to non-touch mode, oldFocus is null. + * When the view tree transitions from non-touch mode to touch mode, newFocus is + * null. When focus changes in non-touch mode (without transition from or to + * touch mode) either oldFocus or newFocus can be null. + * + * @param oldFocus The previously focused view, if any. + * @param newFocus The newly focused View, if any. + */ + public void onGlobalFocusChanged(View oldFocus, View newFocus); + } + + /** + * Interface definition for a callback to be invoked when the global layout state + * or the visibility of views within the view tree changes. + */ + public interface OnGlobalLayoutListener { + /** + * Callback method to be invoked when the global layout state or the visibility of views + * within the view tree changes + */ + public void onGlobalLayout(); + } + + /** + * Interface definition for a callback to be invoked when the view tree is about to be drawn. + */ + public interface OnPreDrawListener { + /** + * Callback method to be invoked when the view tree is about to be drawn. At this point, all + * views in the tree have been measured and given a frame. Clients can use this to adjust + * their scroll bounds or even to request a new layout before drawing occurs. + * + * @return Return true to proceed with the current drawing pass, or false to cancel. + * + * @see android.view.View#onMeasure + * @see android.view.View#onLayout + * @see android.view.View#onDraw + */ + public boolean onPreDraw(); + } + + /** + * Interface definition for a callback to be invoked when the touch mode changes. + */ + public interface OnTouchModeChangeListener { + /** + * Callback method to be invoked when the touch mode changes. + * + * @param isInTouchMode True if the view hierarchy is now in touch mode, false otherwise. + */ + public void onTouchModeChanged(boolean isInTouchMode); + } + + /** + * Creates a new ViewTreeObserver. This constructor should not be called + */ + ViewTreeObserver() { + } + + /** + * Merges all the listeners registered on the specified observer with the listeners + * registered on this object. After this method is invoked, the specified observer + * will return false in {@link #isAlive()} and should not be used anymore. + * + * @param observer The ViewTreeObserver whose listeners must be added to this observer + */ + void merge(ViewTreeObserver observer) { + if (observer.mOnGlobalFocusListeners != null) { + if (mOnGlobalFocusListeners != null) { + mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners); + } else { + mOnGlobalFocusListeners = observer.mOnGlobalFocusListeners; + } + } + + if (observer.mOnGlobalLayoutListeners != null) { + if (mOnGlobalLayoutListeners != null) { + mOnGlobalLayoutListeners.addAll(observer.mOnGlobalLayoutListeners); + } else { + mOnGlobalLayoutListeners = observer.mOnGlobalLayoutListeners; + } + } + + if (observer.mOnPreDrawListeners != null) { + if (mOnPreDrawListeners != null) { + mOnPreDrawListeners.addAll(observer.mOnPreDrawListeners); + } else { + mOnPreDrawListeners = observer.mOnPreDrawListeners; + } + } + + if (observer.mOnTouchModeChangeListeners != null) { + if (mOnTouchModeChangeListeners != null) { + mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners); + } else { + mOnTouchModeChangeListeners = observer.mOnTouchModeChangeListeners; + } + } + + observer.kill(); + } + + /** + * Register a callback to be invoked when the focus state within the view tree changes. + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + */ + public void addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener listener) { + checkIsAlive(); + + if (mOnGlobalFocusListeners == null) { + mOnGlobalFocusListeners = new ArrayList<OnGlobalFocusChangeListener>(); + } + + mOnGlobalFocusListeners.add(listener); + } + + /** + * Remove a previously installed focus change callback. + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnGlobalFocusChangeListener(OnGlobalFocusChangeListener) + */ + public void removeOnGlobalFocusChangeListener(OnGlobalFocusChangeListener victim) { + checkIsAlive(); + if (mOnGlobalFocusListeners == null) { + return; + } + mOnGlobalFocusListeners.remove(victim); + } + + /** + * Register a callback to be invoked when the global layout state or the visibility of views + * within the view tree changes + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + */ + public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) { + checkIsAlive(); + + if (mOnGlobalLayoutListeners == null) { + mOnGlobalLayoutListeners = new ArrayList<OnGlobalLayoutListener>(); + } + + mOnGlobalLayoutListeners.add(listener); + } + + /** + * Remove a previously installed global layout callback + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) + */ + public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) { + checkIsAlive(); + if (mOnGlobalLayoutListeners == null) { + return; + } + mOnGlobalLayoutListeners.remove(victim); + } + + /** + * Register a callback to be invoked when the view tree is about to be drawn + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + */ + public void addOnPreDrawListener(OnPreDrawListener listener) { + checkIsAlive(); + + if (mOnPreDrawListeners == null) { + mOnPreDrawListeners = new ArrayList<OnPreDrawListener>(); + } + + mOnPreDrawListeners.add(listener); + } + + /** + * Remove a previously installed pre-draw callback + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnPreDrawListener(OnPreDrawListener) + */ + public void removeOnPreDrawListener(OnPreDrawListener victim) { + checkIsAlive(); + if (mOnPreDrawListeners == null) { + return; + } + mOnPreDrawListeners.remove(victim); + } + + /** + * Register a callback to be invoked when the invoked when the touch mode changes. + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + */ + public void addOnTouchModeChangeListener(OnTouchModeChangeListener listener) { + checkIsAlive(); + + if (mOnTouchModeChangeListeners == null) { + mOnTouchModeChangeListeners = new ArrayList<OnTouchModeChangeListener>(); + } + + mOnTouchModeChangeListeners.add(listener); + } + + /** + * Remove a previously installed touch mode change callback + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnTouchModeChangeListener(OnTouchModeChangeListener) + */ + public void removeOnTouchModeChangeListener(OnTouchModeChangeListener victim) { + checkIsAlive(); + if (mOnTouchModeChangeListeners == null) { + return; + } + mOnTouchModeChangeListeners.remove(victim); + } + + private void checkIsAlive() { + if (!mAlive) { + throw new IllegalStateException("This ViewTreeObserver is not alive, call " + + "getViewTreeObserver() again"); + } + } + + /** + * Indicates whether this ViewTreeObserver is alive. When an observer is not alive, + * any call to a method (except this one) will throw an exception. + * + * If an application keeps a long-lived reference to this ViewTreeObserver, it should + * always check for the result of this method before calling any other method. + * + * @return True if this object is alive and be used, false otherwise. + */ + public boolean isAlive() { + return mAlive; + } + + /** + * Marks this ViewTreeObserver as not alive. After invoking this method, invoking + * any other method but {@link #isAlive()} and {@link #kill()} will throw an Exception. + * + * @hide + */ + private void kill() { + mAlive = false; + } + + /** + * Notifies registered listeners that focus has changed. + */ + final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) { + final ArrayList<OnGlobalFocusChangeListener> globaFocusListeners = mOnGlobalFocusListeners; + if (globaFocusListeners != null) { + final int count = globaFocusListeners.size(); + for (int i = count - 1; i >= 0; i--) { + globaFocusListeners.get(i).onGlobalFocusChanged(oldFocus, newFocus); + } + } + } + + /** + * Notifies registered listeners that a global layout happened. This can be called + * manually if you are forcing a layout on a View or a hierarchy of Views that are + * not attached to a Window or in the GONE state. + */ + public final void dispatchOnGlobalLayout() { + final ArrayList<OnGlobalLayoutListener> globaLayoutListeners = mOnGlobalLayoutListeners; + if (globaLayoutListeners != null) { + final int count = globaLayoutListeners.size(); + for (int i = count - 1; i >= 0; i--) { + globaLayoutListeners.get(i).onGlobalLayout(); + } + } + } + + /** + * Notifies registered listeners that the drawing pass is about to start. If a + * listener returns true, then the drawing pass is canceled and rescheduled. This can + * be called manually if you are forcing the drawing on a View or a hierarchy of Views + * that are not attached to a Window or in the GONE state. + * + * @return True if the current draw should be canceled and resceduled, false otherwise. + */ + public final boolean dispatchOnPreDraw() { + boolean cancelDraw = false; + final ArrayList<OnPreDrawListener> preDrawListeners = mOnPreDrawListeners; + if (preDrawListeners != null) { + final int count = preDrawListeners.size(); + for (int i = count - 1; i >= 0; i--) { + cancelDraw |= !preDrawListeners.get(i).onPreDraw(); + } + } + return cancelDraw; + } + + /** + * Notifies registered listeners that the touch mode has changed. + * + * @param inTouchMode True if the touch mode is now enabled, false otherwise. + */ + final void dispatchOnTouchModeChanged(boolean inTouchMode) { + final ArrayList<OnTouchModeChangeListener> touchModeListeners = mOnTouchModeChangeListeners; + if (touchModeListeners != null) { + final int count = touchModeListeners.size(); + for (int i = count - 1; i >= 0; i--) { + touchModeListeners.get(i).onTouchModeChanged(inTouchMode); + } + } + } +} diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java new file mode 100644 index 0000000..24f4853 --- /dev/null +++ b/core/java/android/view/VolumePanel.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2007 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.view; + +import android.media.ToneGenerator; +import android.media.AudioManager; +import android.media.AudioService; +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.os.Vibrator; +import android.util.Config; +import android.util.Log; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +/** + * Handle the volume up and down keys. + * + * This code really should be moved elsewhere. + * + * @hide + */ +public class VolumePanel extends Handler +{ + private static final String TAG = "VolumePanel"; + private static boolean LOGD = false || Config.LOGD; + + /** + * The delay before playing a sound. This small period exists so the user + * can press another key (non-volume keys, too) to have it NOT be audible. + * <p> + * PhoneWindow will implement this part. + */ + public static final int PLAY_SOUND_DELAY = 300; + + /** + * The delay before vibrating. This small period exists so if the user is + * moving to silent mode, it will not emit a short vibrate (it normally + * would since vibrate is between normal mode and silent mode using hardware + * keys). + */ + public static final int VIBRATE_DELAY = 300; + + private static final int VIBRATE_DURATION = 300; + private static final int BEEP_DURATION = 150; + private static final int MAX_VOLUME = 100; + private static final int FREE_DELAY = 10000; + + private static final int MSG_VOLUME_CHANGED = 0; + private static final int MSG_FREE_RESOURCES = 1; + private static final int MSG_PLAY_SOUND = 2; + private static final int MSG_STOP_SOUNDS = 3; + private static final int MSG_VIBRATE = 4; + + private final String RINGTONE_VOLUME_TEXT; + private final String MUSIC_VOLUME_TEXT; + private final String INCALL_VOLUME_TEXT; + private final String ALARM_VOLUME_TEXT; + private final String UNKNOWN_VOLUME_TEXT; + + protected Context mContext; + protected AudioService mAudioService; + + private Toast mToast; + private View mView; + private TextView mMessage; + private ImageView mOtherStreamIcon; + private ImageView mRingerStreamIcon; + private ProgressBar mLevel; + + // Synchronize when accessing this + private ToneGenerator mToneGenerators[]; + private Vibrator mVibrator; + + public VolumePanel(Context context, AudioService volumeService) { + mContext = context; + mAudioService = volumeService; + mToast = new Toast(context); + + RINGTONE_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_ringtone); + MUSIC_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_music); + INCALL_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_call); + ALARM_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_alarm); + UNKNOWN_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_unknown); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null); + mMessage = (TextView) view.findViewById(com.android.internal.R.id.message); + mOtherStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon); + mRingerStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon); + mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level); + + mToneGenerators = new ToneGenerator[AudioManager.NUM_STREAMS]; + mVibrator = new Vibrator(); + } + + public void postVolumeChanged(int streamType, int flags) { + if (hasMessages(MSG_VOLUME_CHANGED)) return; + removeMessages(MSG_FREE_RESOURCES); + obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget(); + } + + /** + * Override this if you have other work to do when the volume changes (for + * example, vibrating, playing a sound, etc.). Make sure to call through to + * the superclass implementation. + */ + protected void onVolumeChanged(int streamType, int flags) { + + if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); + + if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { + onShowVolumeChanged(streamType, flags); + } + + if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0) { + removeMessages(MSG_PLAY_SOUND); + sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY); + } + + if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) { + removeMessages(MSG_PLAY_SOUND); + removeMessages(MSG_VIBRATE); + onStopSounds(); + } + + removeMessages(MSG_FREE_RESOURCES); + sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY); + } + + protected void onShowVolumeChanged(int streamType, int flags) { + int index = mAudioService.getStreamVolume(streamType); + String message = UNKNOWN_VOLUME_TEXT; + + if (LOGD) { + Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType + + ", flags: " + flags + "), index: " + index); + } + + switch (streamType) { + + case AudioManager.STREAM_RING: { + message = RINGTONE_VOLUME_TEXT; + setRingerIcon(index); + break; + } + + case AudioManager.STREAM_MUSIC: { + message = MUSIC_VOLUME_TEXT; + setOtherIcon(index); + break; + } + + case AudioManager.STREAM_VOICE_CALL: { + message = INCALL_VOLUME_TEXT; + /* + * For in-call voice call volume, there is no inaudible volume + * level, so never show the mute icon + */ + setOtherIcon(index == 0 ? 1 : index); + break; + } + + case AudioManager.STREAM_ALARM: { + message = ALARM_VOLUME_TEXT; + setOtherIcon(index); + break; + } + } + + if (!mMessage.getText().equals(message)) { + mMessage.setText(message); + } + + int max = mAudioService.getStreamMaxVolume(streamType); + if (max != mLevel.getMax()) { + mLevel.setMax(max); + } + mLevel.setProgress(index); + + mToast.setView(mView); + mToast.setDuration(Toast.LENGTH_SHORT); + mToast.setGravity(Gravity.TOP, 0, 0); + mToast.show(); + + // Do a little vibrate if applicable (only when going into vibrate mode) + if ((flags & AudioManager.FLAG_VIBRATE) != 0 && + mAudioService.isStreamAffectedByRingerMode(streamType) && + mAudioService.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE && + mAudioService.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER)) { + sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY); + } + + } + + protected void onPlaySound(int streamType, int flags) { + + if (hasMessages(MSG_STOP_SOUNDS)) { + removeMessages(MSG_STOP_SOUNDS); + // Force stop right now + onStopSounds(); + } + + synchronized (this) { + ToneGenerator toneGen = getOrCreateToneGenerator(streamType); + toneGen.startTone(ToneGenerator.TONE_PROP_BEEP); + } + + sendMessageDelayed(obtainMessage(MSG_STOP_SOUNDS), BEEP_DURATION); + } + + protected void onStopSounds() { + + synchronized (this) { + for (int i = AudioManager.NUM_STREAMS - 1; i >= 0; i--) { + ToneGenerator toneGen = mToneGenerators[i]; + if (toneGen != null) { + toneGen.stopTone(); + } + } + } + } + + protected void onVibrate() { + + // Make sure we ended up in vibrate ringer mode + if (mAudioService.getRingerMode() != AudioManager.RINGER_MODE_VIBRATE) { + return; + } + + mVibrator.vibrate(VIBRATE_DURATION); + } + + /** + * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. + */ + private ToneGenerator getOrCreateToneGenerator(int streamType) { + synchronized (this) { + if (mToneGenerators[streamType] == null) { + return mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); + } else { + return mToneGenerators[streamType]; + } + } + } + + private void setOtherIcon(int index) { + mRingerStreamIcon.setVisibility(View.GONE); + mOtherStreamIcon.setVisibility(View.VISIBLE); + + mOtherStreamIcon.setImageResource(index == 0 + ? com.android.internal.R.drawable.ic_volume_off_small + : com.android.internal.R.drawable.ic_volume_small); + } + + private void setRingerIcon(int index) { + mOtherStreamIcon.setVisibility(View.GONE); + mRingerStreamIcon.setVisibility(View.VISIBLE); + + int ringerMode = mAudioService.getRingerMode(); + int icon; + + if (LOGD) Log.d(TAG, "setRingerIcon(index: " + index+ "), ringerMode: " + ringerMode); + + if (ringerMode == AudioManager.RINGER_MODE_SILENT) { + icon = com.android.internal.R.drawable.ic_volume_off; + } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + icon = com.android.internal.R.drawable.ic_vibrate; + } else { + icon = com.android.internal.R.drawable.ic_volume; + } + mRingerStreamIcon.setImageResource(icon); + } + + protected void onFreeResources() { + // We'll keep the views, just ditch the cached drawable and hence + // bitmaps + mOtherStreamIcon.setImageDrawable(null); + mRingerStreamIcon.setImageDrawable(null); + + synchronized (this) { + for (int i = mToneGenerators.length - 1; i >= 0; i--) { + if (mToneGenerators[i] != null) { + mToneGenerators[i].release(); + } + mToneGenerators[i] = null; + } + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + + case MSG_VOLUME_CHANGED: { + onVolumeChanged(msg.arg1, msg.arg2); + break; + } + + case MSG_FREE_RESOURCES: { + onFreeResources(); + break; + } + + case MSG_STOP_SOUNDS: { + onStopSounds(); + break; + } + + case MSG_PLAY_SOUND: { + onPlaySound(msg.arg1, msg.arg2); + break; + } + + case MSG_VIBRATE: { + onVibrate(); + break; + } + + } + } + +} diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java new file mode 100644 index 0000000..4aeab2d --- /dev/null +++ b/core/java/android/view/Window.java @@ -0,0 +1,962 @@ +/* + * Copyright (C) 2006 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.view; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; + +/** + * Abstract base class for a top-level window look and behavior policy. An + * instance of this class should be used as the top-level view added to the + * window manager. It provides standard UI policies such as a background, title + * area, default key processing, etc. + * + * <p>The only existing implementation of this abstract class is + * android.policy.PhoneWindow, which you should instantiate when needing a + * Window. Eventually that class will be refactored and a factory method + * added for creating Window instances without knowing about a particular + * implementation. + */ +public abstract class Window { + /** Flag for the "options panel" feature. This is enabled by default. */ + public static final int FEATURE_OPTIONS_PANEL = 0; + /** Flag for the "no title" feature, turning off the title at the top + * of the screen. */ + public static final int FEATURE_NO_TITLE = 1; + /** Flag for the progress indicator feature */ + public static final int FEATURE_PROGRESS = 2; + /** Flag for having an icon on the left side of the title bar */ + public static final int FEATURE_LEFT_ICON = 3; + /** Flag for having an icon on the right side of the title bar */ + public static final int FEATURE_RIGHT_ICON = 4; + /** Flag for indeterminate progress */ + public static final int FEATURE_INDETERMINATE_PROGRESS = 5; + /** Flag for the context menu. This is enabled by default. */ + public static final int FEATURE_CONTEXT_MENU = 6; + /** Flag for custom title. You cannot combine this feature with other title features. */ + public static final int FEATURE_CUSTOM_TITLE = 7; + /* Flag for asking for an OpenGL enabled window. + All 2D graphics will be handled by OpenGL ES. + Private for now, until it is better tested (not shipping in 1.0) + */ + private static final int FEATURE_OPENGL = 8; + /** Flag for setting the progress bar's visibility to VISIBLE */ + public static final int PROGRESS_VISIBILITY_ON = -1; + /** Flag for setting the progress bar's visibility to GONE */ + public static final int PROGRESS_VISIBILITY_OFF = -2; + /** Flag for setting the progress bar's indeterminate mode on */ + public static final int PROGRESS_INDETERMINATE_ON = -3; + /** Flag for setting the progress bar's indeterminate mode off */ + public static final int PROGRESS_INDETERMINATE_OFF = -4; + /** Starting value for the (primary) progress */ + public static final int PROGRESS_START = 0; + /** Ending value for the (primary) progress */ + public static final int PROGRESS_END = 10000; + /** Lowest possible value for the secondary progress */ + public static final int PROGRESS_SECONDARY_START = 20000; + /** Highest possible value for the secondary progress */ + public static final int PROGRESS_SECONDARY_END = 30000; + + /** The default features enabled */ + @SuppressWarnings({"PointlessBitwiseExpression"}) + protected static final int DEFAULT_FEATURES = (1 << FEATURE_OPTIONS_PANEL) | + (1 << FEATURE_CONTEXT_MENU); + + /** + * The ID that the main layout in the XML layout file should have. + */ + public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; + + private final Context mContext; + + private TypedArray mWindowStyle; + private Callback mCallback; + private WindowManager mWindowManager; + private IBinder mAppToken; + private String mAppName; + private Window mContainer; + private Window mActiveChild; + private boolean mIsActive = false; + private boolean mHasChildren = false; + private int mForcedWindowFlags = 0; + + private int mFeatures = DEFAULT_FEATURES; + private int mLocalFeatures = DEFAULT_FEATURES; + + private boolean mHaveWindowFormat = false; + private int mDefaultWindowFormat = PixelFormat.OPAQUE; + + // The current window attributes. + private final WindowManager.LayoutParams mWindowAttributes = + new WindowManager.LayoutParams(); + + /** + * API from a Window back to its caller. This allows the client to + * intercept key dispatching, panels and menus, etc. + */ + public interface Callback { + /** + * Called to process key events. At the very least your + * implementation must call + * {@link android.view.Window#superDispatchKeyEvent} to do the + * standard key processing. + * + * @param event The key event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchKeyEvent(KeyEvent event); + + /** + * Called to process touch screen events. At the very least your + * implementation must call + * {@link android.view.Window#superDispatchTouchEvent} to do the + * standard touch screen processing. + * + * @param event The touch screen event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTouchEvent(MotionEvent event); + + /** + * Called to process trackball events. At the very least your + * implementation must call + * {@link android.view.Window#superDispatchTrackballEvent} to do the + * standard trackball processing. + * + * @param event The trackball event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTrackballEvent(MotionEvent event); + + /** + * Instantiate the view to display in the panel for 'featureId'. + * You can return null, in which case the default content (typically + * a menu) will be created for you. + * + * @param featureId Which panel is being created. + * + * @return view The top-level view to place in the panel. + * + * @see #onPreparePanel + */ + public View onCreatePanelView(int featureId); + + /** + * Initialize the contents of the menu for panel 'featureId'. This is + * called if onCreatePanelView() returns null, giving you a standard + * menu in which you can place your items. It is only called once for + * the panel, the first time it is shown. + * + * <p>You can safely hold on to <var>menu</var> (and any items created + * from it), making modifications to it as desired, until the next + * time onCreatePanelMenu() is called for this feature. + * + * @param featureId The panel being created. + * @param menu The menu inside the panel. + * + * @return boolean You must return true for the panel to be displayed; + * if you return false it will not be shown. + */ + public boolean onCreatePanelMenu(int featureId, Menu menu); + + /** + * Prepare a panel to be displayed. This is called right before the + * panel window is shown, every time it is shown. + * + * @param featureId The panel that is being displayed. + * @param view The View that was returned by onCreatePanelView(). + * @param menu If onCreatePanelView() returned null, this is the Menu + * being displayed in the panel. + * + * @return boolean You must return true for the panel to be displayed; + * if you return false it will not be shown. + * + * @see #onCreatePanelView + */ + public boolean onPreparePanel(int featureId, View view, Menu menu); + + /** + * Called when a panel's menu is opened by the user. This may also be + * called when the menu is changing from one type to another (for + * example, from the icon menu to the expanded menu). + * + * @param featureId The panel that the menu is in. + * @param menu The menu that is opened. + * @return Return true to allow the menu to open, or false to prevent + * the menu from opening. + */ + public boolean onMenuOpened(int featureId, Menu menu); + + /** + * Called when a panel's menu item has been selected by the user. + * + * @param featureId The panel that the menu is in. + * @param item The menu item that was selected. + * + * @return boolean Return true to finish processing of selection, or + * false to perform the normal menu handling (calling its + * Runnable or sending a Message to its target Handler). + */ + public boolean onMenuItemSelected(int featureId, MenuItem item); + + /** + * This is called whenever the current window attributes change. + * + + */ + public void onWindowAttributesChanged(WindowManager.LayoutParams attrs); + + /** + * This hook is called whenever the content view of the screen changes + * (due to a call to + * {@link Window#setContentView(View, android.view.ViewGroup.LayoutParams) + * Window.setContentView} or + * {@link Window#addContentView(View, android.view.ViewGroup.LayoutParams) + * Window.addContentView}). + */ + public void onContentChanged(); + + /** + * This hook is called whenever the window focus changes. + * + * @param hasFocus Whether the window now has focus. + */ + public void onWindowFocusChanged(boolean hasFocus); + + /** + * Called when a panel is being closed. If another logical subsequent + * panel is being opened (and this panel is being closed to make room for the subsequent + * panel), this method will NOT be called. + * + * @param featureId The panel that is being displayed. + * @param menu If onCreatePanelView() returned null, this is the Menu + * being displayed in the panel. + */ + public void onPanelClosed(int featureId, Menu menu); + + /** + * Called when the user signals the desire to start a search. + * + * @return true if search launched, false if activity refuses (blocks) + * + * @see android.app.Activity#onSearchRequested() + */ + public boolean onSearchRequested(); + } + + public Window(Context context) { + mContext = context; + } + + /** + * Return the Context this window policy is running in, for retrieving + * resources and other information. + * + * @return Context The Context that was supplied to the constructor. + */ + public final Context getContext() { + return mContext; + } + + /** + * Return the {@link android.R.styleable#Window} attributes from this + * window's theme. + */ + public final TypedArray getWindowStyle() { + synchronized (this) { + if (mWindowStyle == null) { + mWindowStyle = mContext.obtainStyledAttributes( + com.android.internal.R.styleable.Window); + } + return mWindowStyle; + } + } + + /** + * Set the container for this window. If not set, the DecorWindow + * operates as a top-level window; otherwise, it negotiates with the + * container to display itself appropriately. + * + * @param container The desired containing Window. + */ + public void setContainer(Window container) { + mContainer = container; + if (container != null) { + // Embedded screens never have a title. + mFeatures |= 1<<FEATURE_NO_TITLE; + mLocalFeatures |= 1<<FEATURE_NO_TITLE; + container.mHasChildren = true; + } + } + + /** + * Return the container for this Window. + * + * @return Window The containing window, or null if this is a + * top-level window. + */ + public final Window getContainer() { + return mContainer; + } + + public final boolean hasChildren() { + return mHasChildren; + } + + /** + * Set the window manager for use by this Window to, for example, + * display panels. This is <em>not</em> used for displaying the + * Window itself -- that must be done by the client. + * + * @param wm The ViewManager for adding new windows. + */ + public void setWindowManager(WindowManager wm, + IBinder appToken, String appName) { + mAppToken = appToken; + mAppName = appName; + if (wm == null) { + wm = WindowManagerImpl.getDefault(); + } + mWindowManager = new LocalWindowManager(wm); + } + + private class LocalWindowManager implements WindowManager { + LocalWindowManager(WindowManager wm) { + mWindowManager = wm; + } + + public final void addView(View view, ViewGroup.LayoutParams params) { + // Let this throw an exception on a bad params. + WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params; + CharSequence curTitle = wp.getTitle(); + if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && + wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + if (wp.token == null) { + View decor = peekDecorView(); + if (decor != null) { + wp.token = decor.getWindowToken(); + } + } + if (curTitle == null || curTitle.length() == 0) { + String title; + if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) { + title="Media"; + } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { + title="Panel"; + } else { + title=Integer.toString(wp.type); + } + if (mAppName != null) { + title += ":" + mAppName; + } + wp.setTitle(title); + } + } else { + if (wp.token == null) { + wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; + } + if ((curTitle == null || curTitle.length() == 0) + && mAppName != null) { + wp.setTitle(mAppName); + } + if (wp.windowAnimations == 0) { + wp.windowAnimations = getWindowStyle().getResourceId( + com.android.internal.R.styleable.Window_windowAnimationStyle, 0); + } + } + if (wp.packageName == null) { + wp.packageName = mContext.getPackageName(); + } + mWindowManager.addView(view, params); + } + + public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + mWindowManager.updateViewLayout(view, params); + } + + public final void removeView(View view) { + mWindowManager.removeView(view); + } + + public final void removeViewImmediate(View view) { + mWindowManager.removeViewImmediate(view); + } + + public Display getDefaultDisplay() { + return mWindowManager.getDefaultDisplay(); + } + + WindowManager mWindowManager; + } + + /** + * Return the window manager allowing this Window to display its own + * windows. + * + * @return WindowManager The ViewManager. + */ + public WindowManager getWindowManager() { + return mWindowManager; + } + + /** + * Set the Callback interface for this window, used to intercept key + * events and other dynamic operations in the window. + * + * @param callback The desired Callback interface. + */ + public void setCallback(Callback callback) { + mCallback = callback; + } + + /** + * Return the current Callback interface for this window. + */ + public final Callback getCallback() { + return mCallback; + } + + /** + * Return whether this window is being displayed with a floating style + * (based on the {@link android.R.attr#windowIsFloating} attribute in + * the style/theme). + * + * @return Returns true if the window is configured to be displayed floating + * on top of whatever is behind it. + */ + public abstract boolean isFloating(); + + /** + * Set the width and height layout parameters of the window. The default + * for both of these is FILL_PARENT; you can change them to WRAP_CONTENT to + * make a window that is not full-screen. + * + * @param width The desired layout width of the window. + * @param height The desired layout height of the window. + */ + public void setLayout(int width, int height) + { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.width = width; + attrs.height = height; + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + + /** + * Set the gravity of the window, as per the Gravity constants. This + * controls how the window manager is positioned in the overall window; it + * is only useful when using WRAP_CONTENT for the layout width or height. + * + * @param gravity The desired gravity constant. + * + * @see Gravity + * @see #setLayout + */ + public void setGravity(int gravity) + { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.gravity = gravity; + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + + /** + * Set the type of the window, as per the WindowManager.LayoutParams + * types. + * + * @param type The new window type (see WindowManager.LayoutParams). + */ + public void setType(int type) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.type = type; + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + + /** + * Set the format of window, as per the PixelFormat types. This overrides + * the default format that is selected by the Window based on its + * window decorations. + * + * @param format The new window format (see PixelFormat). Use + * PixelFormat.UNKNOWN to allow the Window to select + * the format. + * + * @see PixelFormat + */ + public void setFormat(int format) { + final WindowManager.LayoutParams attrs = getAttributes(); + if (format != PixelFormat.UNKNOWN) { + attrs.format = format; + mHaveWindowFormat = true; + } else { + attrs.format = mDefaultWindowFormat; + mHaveWindowFormat = false; + } + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + + /** + * Convenience function to set the flag bits as specified in flags, as + * per {@link #setFlags}. + * @param flags The flag bits to be set. + * @see #setFlags + */ + public void addFlags(int flags) { + setFlags(flags, flags); + } + + /** + * Convenience function to clear the flag bits as specified in flags, as + * per {@link #setFlags}. + * @param flags The flag bits to be cleared. + * @see #setFlags + */ + public void clearFlags(int flags) { + setFlags(0, flags); + } + + /** + * Set the flags of the window, as per the + * {@link WindowManager.LayoutParams WindowManager.LayoutParams} + * flags. + * + * <p>Note that some flags must be set before the window decoration is + * created (by the first call to + * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} or + * {@link #getDecorView()}: + * {@link WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN} and + * {@link WindowManager.LayoutParams#FLAG_LAYOUT_INSET_DECOR}. These + * will be set for you based on the {@link android.R.attr#windowIsFloating} + * attribute. + * + * @param flags The new window flags (see WindowManager.LayoutParams). + * @param mask Which of the window flag bits to modify. + */ + public void setFlags(int flags, int mask) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.flags = (attrs.flags&~mask) | (flags&mask); + mForcedWindowFlags |= mask; + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + + /** + * Specify custom window attributes. + * + * @param a The new window attributes, which will completely override any + * current values. + */ + public void setAttributes(WindowManager.LayoutParams a) { + mWindowAttributes.copyFrom(a); + mForcedWindowFlags = 0xffffffff; + if (mCallback != null) { + mCallback.onWindowAttributesChanged(mWindowAttributes); + } + } + + /** + * Retrieve the current window attributes associated with this panel. + * + * @return WindowManager.LayoutParams Either the existing window + * attributes object, or a freshly created one if there is none. + */ + public final WindowManager.LayoutParams getAttributes() { + return mWindowAttributes; + } + + /** + * Return the window flags that have been explicitly set by the client, + * so will not be modified by {@link #getDecorView}. + */ + protected final int getForcedWindowFlags() { + return mForcedWindowFlags; + } + + /** + * Enable extended screen features. This must be called before + * setContentView(). May be called as many times as desired as long as it + * is before setContentView(). If not called, no extended features + * will be available. You can not turn off a feature once it is requested. + * You canot use other title features with {@link #FEATURE_CUSTOM_TITLE}. + * + * @param featureId The desired features, defined as constants by Window. + * @return The features that are now set. + */ + public boolean requestFeature(int featureId) { + final int flag = 1<<featureId; + mFeatures |= flag; + mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag; + return (mFeatures&flag) != 0; + } + + public final void makeActive() { + if (mContainer != null) { + if (mContainer.mActiveChild != null) { + mContainer.mActiveChild.mIsActive = false; + } + mContainer.mActiveChild = this; + } + mIsActive = true; + onActive(); + } + + public final boolean isActive() + { + return mIsActive; + } + + /** + * Finds a view that was identified by the id attribute from the XML that + * was processed in {@link android.app.Activity#onCreate}. This will + * implicitly call {@link #getDecorView} for you, with all of the + * associated side-effects. + * + * @return The view if found or null otherwise. + */ + public View findViewById(int id) { + return getDecorView().findViewById(id); + } + + /** + * Convenience for + * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} + * to set the screen content from a layout resource. The resource will be + * inflated, adding all top-level views to the screen. + * + * @param layoutResID Resource ID to be inflated. + * @see #setContentView(View, android.view.ViewGroup.LayoutParams) + */ + public abstract void setContentView(int layoutResID); + + /** + * Convenience for + * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} + * set the screen content to an explicit view. This view is placed + * directly into the screen's view hierarchy. It can itself be a complex + * view hierarhcy. + * + * @param view The desired content to display. + * @see #setContentView(View, android.view.ViewGroup.LayoutParams) + */ + public abstract void setContentView(View view); + + /** + * Set the screen content to an explicit view. This view is placed + * directly into the screen's view hierarchy. It can itself be a complex + * view hierarchy. + * + * <p>Note that calling this function "locks in" various characteristics + * of the window that can not, from this point forward, be changed: the + * features that have been requested with {@link #requestFeature(int)}, + * and certain window flags as described in {@link #setFlags(int, int)}. + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public abstract void setContentView(View view, ViewGroup.LayoutParams params); + + /** + * Variation on + * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)} + * to add an additional content view to the screen. Added after any existing + * ones in the screen -- existing views are NOT removed. + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public abstract void addContentView(View view, ViewGroup.LayoutParams params); + + /** + * Return the view in this Window that currently has focus, or null if + * there are none. Note that this does not look in any containing + * Window. + * + * @return View The current View with focus or null. + */ + public abstract View getCurrentFocus(); + + /** + * Quick access to the {@link LayoutInflater} instance that this Window + * retrieved from its Context. + * + * @return LayoutInflater The shared LayoutInflater. + */ + public abstract LayoutInflater getLayoutInflater(); + + public abstract void setTitle(CharSequence title); + + public abstract void setTitleColor(int textColor); + + public abstract void openPanel(int featureId, KeyEvent event); + + public abstract void closePanel(int featureId); + + public abstract void togglePanel(int featureId, KeyEvent event); + + public abstract boolean performPanelShortcut(int featureId, + int keyCode, + KeyEvent event, + int flags); + public abstract boolean performPanelIdentifierAction(int featureId, + int id, + int flags); + + public abstract void closeAllPanels(); + + public abstract boolean performContextMenuIdentifierAction(int id, int flags); + + /** + * Should be called when the configuration is changed. + * + * @param newConfig The new configuration. + */ + public abstract void onConfigurationChanged(Configuration newConfig); + + /** + * Change the background of this window to a Drawable resource. Setting the + * background to null will make the window be opaque. To make the window + * transparent, you can use an empty drawable (for instance a ColorDrawable + * with the color 0 or the system drawable android:drawable/empty.) + * + * @param resid The resource identifier of a drawable resource which will be + * installed as the new background. + */ + public void setBackgroundDrawableResource(int resid) + { + setBackgroundDrawable(mContext.getResources().getDrawable(resid)); + } + + /** + * Change the background of this window to a custom Drawable. Setting the + * background to null will make the window be opaque. To make the window + * transparent, you can use an empty drawable (for instance a ColorDrawable + * with the color 0 or the system drawable android:drawable/empty.) + * + * @param drawable The new Drawable to use for this window's background. + */ + public abstract void setBackgroundDrawable(Drawable drawable); + + /** + * Set the value for a drawable feature of this window, from a resource + * identifier. You must have called requestFeauture(featureId) before + * calling this function. + * + * @see android.content.res.Resources#getDrawable(int) + * + * @param featureId The desired drawable feature to change, defined as a + * constant by Window. + * @param resId Resource identifier of the desired image. + */ + public abstract void setFeatureDrawableResource(int featureId, int resId); + + /** + * Set the value for a drawable feature of this window, from a URI. You + * must have called requestFeature(featureId) before calling this + * function. + * + * <p>The only URI currently supported is "content:", specifying an image + * in a content provider. + * + * @see android.widget.ImageView#setImageURI + * + * @param featureId The desired drawable feature to change. Features are + * constants defined by Window. + * @param uri The desired URI. + */ + public abstract void setFeatureDrawableUri(int featureId, Uri uri); + + /** + * Set an explicit Drawable value for feature of this window. You must + * have called requestFeature(featureId) before calling this function. + * + * @param featureId The desired drawable feature to change. + * Features are constants defined by Window. + * @param drawable A Drawable object to display. + */ + public abstract void setFeatureDrawable(int featureId, Drawable drawable); + + /** + * Set a custom alpha value for the given drawale feature, controlling how + * much the background is visible through it. + * + * @param featureId The desired drawable feature to change. + * Features are constants defined by Window. + * @param alpha The alpha amount, 0 is completely transparent and 255 is + * completely opaque. + */ + public abstract void setFeatureDrawableAlpha(int featureId, int alpha); + + /** + * Set the integer value for a feature. The range of the value depends on + * the feature being set. For FEATURE_PROGRESSS, it should go from 0 to + * 10000. At 10000 the progress is complete and the indicator hidden. + * + * @param featureId The desired feature to change. + * Features are constants defined by Window. + * @param value The value for the feature. The interpretation of this + * value is feature-specific. + */ + public abstract void setFeatureInt(int featureId, int value); + + /** + * Request that key events come to this activity. Use this if your + * activity has no views with focus, but the activity still wants + * a chance to process key events. + */ + public abstract void takeKeyEvents(boolean get); + + /** + * Used by custom windows, such as Dialog, to pass the key press event + * further down the view hierarchy. Application developers should + * not need to implement or call this. + * + */ + public abstract boolean superDispatchKeyEvent(KeyEvent event); + + /** + * Used by custom windows, such as Dialog, to pass the touch screen event + * further down the view hierarchy. Application developers should + * not need to implement or call this. + * + */ + public abstract boolean superDispatchTouchEvent(MotionEvent event); + + /** + * Used by custom windows, such as Dialog, to pass the trackball event + * further down the view hierarchy. Application developers should + * not need to implement or call this. + * + */ + public abstract boolean superDispatchTrackballEvent(MotionEvent event); + + /** + * Retrieve the top-level window decor view (containing the standard + * window frame/decorations and the client's content inside of that), which + * can be added as a window to the window manager. + * + * <p><em>Note that calling this function for the first time "locks in" + * various window characteristics as described in + * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p> + * + * @return Returns the top-level window decor view. + */ + public abstract View getDecorView(); + + /** + * Retrieve the current decor view, but only if it has already been created; + * otherwise returns null. + * + * @return Returns the top-level window decor or null. + * @see #getDecorView + */ + public abstract View peekDecorView(); + + public abstract Bundle saveHierarchyState(); + + public abstract void restoreHierarchyState(Bundle savedInstanceState); + + protected abstract void onActive(); + + /** + * Return the feature bits that are enabled. This is the set of features + * that were given to requestFeature(), and are being handled by this + * Window itself or its container. That is, it is the set of + * requested features that you can actually use. + * + * <p>To do: add a public version of this API that allows you to check for + * features by their feature ID. + * + * @return int The feature bits. + */ + protected final int getFeatures() + { + return mFeatures; + } + + /** + * Return the feature bits that are being implemented by this Window. + * This is the set of features that were given to requestFeature(), and are + * being handled by only this Window itself, not by its containers. + * + * @return int The feature bits. + */ + protected final int getLocalFeatures() + { + return mLocalFeatures; + } + + /** + * Set the default format of window, as per the PixelFormat types. This + * is the format that will be used unless the client specifies in explicit + * format with setFormat(); + * + * @param format The new window format (see PixelFormat). + * + * @see #setFormat + * @see PixelFormat + */ + protected void setDefaultWindowFormat(int format) + { + mDefaultWindowFormat = format; + if (!mHaveWindowFormat) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.format = format; + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + } + + public abstract void setChildDrawable(int featureId, Drawable drawable); + + public abstract void setChildInt(int featureId, int value); + + /** + * Is a keypress one of the defined shortcut keys for this window. + * @param keyCode the key code from {@link android.view.KeyEvent} to check. + * @param event the {@link android.view.KeyEvent} to use to help check. + */ + public abstract boolean isShortcutKey(int keyCode, KeyEvent event); + + /** + * @see android.app.Activity#setVolumeControlStream(int) + */ + public abstract void setVolumeControlStream(int streamType); + + /** + * @see android.app.Activity#getVolumeControlStream() + */ + public abstract int getVolumeControlStream(); + +} diff --git a/core/java/android/view/WindowManager.aidl b/core/java/android/view/WindowManager.aidl new file mode 100755 index 0000000..556dc72 --- /dev/null +++ b/core/java/android/view/WindowManager.aidl @@ -0,0 +1,21 @@ +/* //device/java/android/android/view/WindowManager.aidl +** +** Copyright 2007, 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.view; + +parcelable WindowManager.LayoutParams; + diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java new file mode 100644 index 0000000..4c1dec5 --- /dev/null +++ b/core/java/android/view/WindowManager.java @@ -0,0 +1,693 @@ +/* + * Copyright (C) 2006 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.view; + +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; + + +/** + * The interface that apps use to talk to the window manager. + * <p> + * Use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> to get one of these. + * + * @see android.content.Context#getSystemService + * @see android.content.Context#WINDOW_SERVICE + */ +public interface WindowManager extends ViewManager { + /** + * Exception that is thrown when trying to add view whose + * {@link WindowManager.LayoutParams} {@link WindowManager.LayoutParams#token} + * is invalid. + */ + public static class BadTokenException extends RuntimeException { + public BadTokenException() { + } + + public BadTokenException(String name) { + super(name); + } + } + + /** + * Use this method to get the default Display object. + * + * @return default Display object + */ + public Display getDefaultDisplay(); + + /** + * Special variation of {@link #removeView} that immediately invokes + * the given view hierarchy's {@link View#onDetachedFromWindow() + * View.onDetachedFromWindow()} methods before returning. This is not + * for normal applications; using it correctly requires great care. + * + * @param view The view to be removed. + */ + public void removeViewImmediate(View view); + + public static class LayoutParams extends ViewGroup.LayoutParams + implements Parcelable { + /** + * X position for this window. With the default gravity it is ignored. + * When using {@link Gravity#LEFT} or {@link Gravity#RIGHT} it provides + * an offset from the given edge. + */ + public int x; + + /** + * Y position for this window. With the default gravity it is ignored. + * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides + * an offset from the given edge. + */ + public int y; + + /** + * Indicates how much of the extra space will be allocated horizontally + * to the view associated with these LayoutParams. Specify 0 if the view + * should not be stretched. Otherwise the extra pixels will be pro-rated + * among all views whose weight is greater than 0. + */ + public float horizontalWeight; + + /** + * Indicates how much of the extra space will be allocated vertically + * to the view associated with these LayoutParams. Specify 0 if the view + * should not be stretched. Otherwise the extra pixels will be pro-rated + * among all views whose weight is greater than 0. + */ + public float verticalWeight; + + /** + * The general type of window. There are three main classes of + * window types: + * <ul> + * <li> <strong>Application windows</strong> (ranging from + * {@link #FIRST_APPLICATION_WINDOW} to + * {@link #LAST_APPLICATION_WINDOW}) are normal top-level application + * windows. For these types of windows, the {@link #token} must be + * set to the token of the activity they are a part of (this will + * normally be done for you if {@link #token} is null). + * <li> <strong>Sub-windows</strong> (ranging from + * {@link #FIRST_SUB_WINDOW} to + * {@link #LAST_SUB_WINDOW}) are associated with another top-level + * window. For these types of windows, the {@link #token} must be + * the token of the window it is attached to. + * <li> <strong>System windows</strong> (ranging from + * {@link #FIRST_SYSTEM_WINDOW} to + * {@link #LAST_SYSTEM_WINDOW}) are special types of windows for + * use by the system for specific purposes. They should not normally + * be used by applications, and a special permission is required + * to use them. + * </ul> + * + * @see #TYPE_BASE_APPLICATION + * @see #TYPE_APPLICATION + * @see #TYPE_APPLICATION_STARTING + * @see #TYPE_APPLICATION_PANEL + * @see #TYPE_APPLICATION_MEDIA + * @see #TYPE_APPLICATION_SUB_PANEL + * @see #TYPE_STATUS_BAR + * @see #TYPE_SEARCH_BAR + * @see #TYPE_PHONE + * @see #TYPE_SYSTEM_ALERT + * @see #TYPE_KEYGUARD + * @see #TYPE_TOAST + * @see #TYPE_SYSTEM_OVERLAY + * @see #TYPE_PRIORITY_PHONE + */ + public int type; + + /** + * Start of window types that represent normal application windows. + */ + public static final int FIRST_APPLICATION_WINDOW = 1; + + /** + * Window type: an application window that serves as the "base" window + * of the overall application; all other application windows will + * appear on top of it. + */ + public static final int TYPE_BASE_APPLICATION = 1; + + /** + * Window type: a normal application window. The {@link #token} must be + * an Activity token identifying who the window belongs to. + */ + public static final int TYPE_APPLICATION = 2; + + /** + * Window type: special application window that is displayed while the + * application is starting. Not for use by applications themselves; + * this is used by the system to display something until the + * application can show its own windows. + */ + public static final int TYPE_APPLICATION_STARTING = 3; + + /** + * End of types of application windows. + */ + public static final int LAST_APPLICATION_WINDOW = 99; + + /** + * Start of types of sub-windows. The {@link #token} of these windows + * must be set to the window they are attached to. These types of + * windows are kept next to their attached window in Z-order, and their + * coordinate space is relative to their attached window. + */ + public static final int FIRST_SUB_WINDOW = 1000; + + /** + * Window type: a panel on top of an application window. These windows + * appear on top of their attached window. + */ + public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; + + /** + * Window type: window for showing media (e.g. video). These windows + * are displayed behind their attached window. + */ + public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; + + /** + * Window type: a sub-panel on top of an application window. These + * windows are displayed on top their attached window and any + * {@link #TYPE_APPLICATION_PANEL} panels. + */ + public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2; + + /** + * End of types of sub-windows. + */ + public static final int LAST_SUB_WINDOW = 1999; + + /** + * Start of system-specific window types. These are not normally + * created by applications. + */ + public static final int FIRST_SYSTEM_WINDOW = 2000; + + /** + * Window type: the status bar. There can be only one status bar + * window; it is placed at the top of the screen, and all other + * windows are shifted down so they are below it. + */ + public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW; + + /** + * Window type: the search bar. There can be only one search bar + * window; it is placed at the top of the screen. + */ + public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1; + + /** + * Window type: phone. These are non-application windows providing + * user interaction with the phone (in particular incoming calls). + * These windows are normally placed above all applications, but behind + * the status bar. + */ + public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; + + /** + * Window type: system window, such as low power alert. These windows + * are always on top of application windows. + */ + public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3; + + /** + * Window type: keyguard window. + */ + public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4; + + /** + * Window type: transient notifications. + */ + public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5; + + /** + * Window type: system overlay windows, which need to be displayed + * on top of everything else. These windows must not take input + * focus, or they will interfere with the keyguard. + */ + public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6; + + /** + * Window type: priority phone UI, which needs to be displayed even if + * the keyguard is active. These windows must not take input + * focus, or they will interfere with the keyguard. + */ + public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7; + + /** + * Window type: panel that slides out from the status bar + */ + public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+8; + + /** + * Window type: panel that slides out from the status bar + */ + public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8; + + /** + * Window type: dialogs that the keyguard shows + */ + public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9; + + /** + * Window type: internal system error windows, appear on top of + * everything they can. + */ + public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10; + + /** + * End of types of system windows. + */ + public static final int LAST_SYSTEM_WINDOW = 2999; + + /** + * Specifies what type of memory buffers should be used by this window. + * Default is normal. + * + * @see #MEMORY_TYPE_NORMAL + * @see #MEMORY_TYPE_HARDWARE + * @see #MEMORY_TYPE_GPU + * @see #MEMORY_TYPE_PUSH_BUFFERS + */ + public int memoryType; + + /** Memory type: The window's surface is allocated in main memory. */ + public static final int MEMORY_TYPE_NORMAL = 0; + /** Memory type: The window's surface is configured to be accessible + * by DMA engines and hardware accelerators. */ + public static final int MEMORY_TYPE_HARDWARE = 1; + /** Memory type: The window's surface is configured to be accessible + * by graphics accelerators. */ + public static final int MEMORY_TYPE_GPU = 2; + /** Memory type: The window's surface doesn't own its buffers and + * therefore cannot be locked. Instead the buffers are pushed to + * it through native binder calls. */ + public static final int MEMORY_TYPE_PUSH_BUFFERS = 3; + + /** + * Various behavioral options/flags. Default is none. + * + * @see #FLAG_BLUR_BEHIND + * @see #FLAG_DIM_BEHIND + * @see #FLAG_NOT_FOCUSABLE + * @see #FLAG_NOT_TOUCHABLE + * @see #FLAG_NOT_TOUCH_MODAL + * @see #FLAG_LAYOUT_IN_SCREEN + * @see #FLAG_DITHER + * @see #FLAG_KEEP_SCREEN_ON + * @see #FLAG_FULLSCREEN + * @see #FLAG_FORCE_NOT_FULLSCREEN + * @see #FLAG_IGNORE_CHEEK_PRESSES + */ + public int flags; + + /** Window flag: everything behind this window will be dimmed. + * Use {@link #dimAmount} to control the amount of dim. */ + public static final int FLAG_DIM_BEHIND = 0x00000002; + + /** Window flag: blur everything behind this window. */ + public static final int FLAG_BLUR_BEHIND = 0x00000004; + + /** Window flag: this window won't ever get focus. */ + public static final int FLAG_NOT_FOCUSABLE = 0x00000008; + + /** Window flag: this window can never receive touch events. */ + public static final int FLAG_NOT_TOUCHABLE = 0x00000010; + + /** Window flag: Even when this window is focusable (its + * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events + * outside of the window to be sent to the windows behind it. Otherwise + * it will consume all pointer events itself, regardless of whether they + * are inside of the window. */ + public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020; + + /** Window flag: When set, if the device is asleep when the touch + * screen is pressed, you will receive this first touch event. Usually + * the first touch event is consumed by the system since the user can + * not see what they are pressing on. + */ + public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040; + + /** Window flag: as long as this window is visible to the user, keep + * the device's screen turned on and bright. */ + public static final int FLAG_KEEP_SCREEN_ON = 0x00000080; + + /** Window flag: place the window within the entire screen, ignoring + * decorations around the border (a.k.a. the status bar). The + * window must correctly position its contents to take the screen + * decoration into account. This flag is normally set for you + * by Window as described in {@link Window#setFlags}. */ + public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100; + + /** Window flag: allow window to extend outside of the screen. */ + public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200; + + /** Window flag: Hide all screen decorations (e.g. status bar) while + * this window is displayed. This allows the window to use the entire + * display space for itself -- the status bar will be hidden when + * an app window with this flag set is on the top layer. */ + public static final int FLAG_FULLSCREEN = 0x00000400; + + /** Window flag: Override {@link #FLAG_FULLSCREEN and force the + * screen decorations (such as status bar) to be shown. */ + public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800; + + /** Window flag: turn on dithering when compositing this window to + * the screen. */ + public static final int FLAG_DITHER = 0x00001000; + + /** Window flag: don't allow screen shots while this window is + * displayed. */ + public static final int FLAG_SECURE = 0x00002000; + + /** Window flag: a special mode where the layout parameters are used + * to perform scaling of the surface when it is composited to the + * screen. */ + public static final int FLAG_SCALED = 0x00004000; + + /** Window flag: intended for windows that will often be used when the user is + * holding the screen against their face, it will aggressively filter the event + * stream to prevent unintended presses in this situation that may not be + * desired for a particular window, when such an event stream is detected, the + * application will receive a CANCEL motion event to indicate this so applications + * can handle this accordingly by taking no action on the event + * until the finger is released. */ + public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000; + + /** Window flag: a special option only for use in combination with + * {@link #FLAG_LAYOUT_IN_SCREEN}. When requesting layout in the + * screen your window may appear on top of or behind screen decorations + * such as the status bar. By also including this flag, the window + * manager will report the inset rectangle needed to ensure your + * content is not covered by screen decorations. This flag is normally + * set for you by Window as described in {@link Window#setFlags}.*/ + public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000; + + /** Window flag: a special option intended for system dialogs. When + * this flag is set, the window will demand focus unconditionally when + * it is created. + * {@hide} */ + public static final int FLAG_SYSTEM_ERROR = 0x40000000; + + /** + * Placement of window within the screen as per {@link Gravity} + * + * @see Gravity + */ + public int gravity; + + /** + * The horizontal margin, as a percentage of the container's width, + * between the container and the widget. + */ + public float horizontalMargin; + + /** + * The vertical margin, as a percentage of the container's height, + * between the container and the widget. + */ + public float verticalMargin; + + /** + * The desired bitmap format. May be one of the constants in + * {@link android.graphics.PixelFormat}. Default is OPAQUE. + */ + public int format; + + /** + * A style resource defining the animations to use for this window. + * This must be a system resource; it can not be an application resource + * because the window manager does not have access to applications. + */ + public int windowAnimations; + + /** + * An alpha value to apply to this entire window. + * An alpha of 1.0 means fully opaque and 0.0 means fully transparent + */ + public float alpha = 1.0f; + + /** + * When {@link #FLAG_DIM_BEHIND} is set, this is the amount of dimming + * to apply. Range is from 1.0 for completely opaque to 0.0 for no + * dim. + */ + public float dimAmount = 1.0f; + + /** + * Identifier for this window. This will usually be filled in for + * you. + */ + public IBinder token = null; + + /** + * Name of the package owning this window. + */ + public String packageName = null; + + public LayoutParams() { + super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + type = TYPE_APPLICATION; + format = PixelFormat.OPAQUE; + } + + public LayoutParams(int _type) { + super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + type = _type; + format = PixelFormat.OPAQUE; + } + + public LayoutParams(int _type, int _flags) { + super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + type = _type; + flags = _flags; + format = PixelFormat.OPAQUE; + } + + public LayoutParams(int _type, int _flags, int _format) { + super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + type = _type; + flags = _flags; + format = _format; + } + + public LayoutParams(int w, int h, int _type, int _flags, int _format) { + super(w, h); + type = _type; + flags = _flags; + format = _format; + } + + public LayoutParams(int w, int h, int xpos, int ypos, int _type, + int _flags, int _format) { + super(w, h); + x = xpos; + y = ypos; + type = _type; + flags = _flags; + format = _format; + } + + public final void setTitle(CharSequence title) { + if (null == title) + title = ""; + + mTitle = TextUtils.stringOrSpannedString(title); + } + + public final CharSequence getTitle() { + return mTitle; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int parcelableFlags) { + out.writeInt(width); + out.writeInt(height); + out.writeInt(x); + out.writeInt(y); + out.writeInt(type); + out.writeInt(memoryType); + out.writeInt(flags); + out.writeInt(gravity); + out.writeFloat(horizontalMargin); + out.writeFloat(verticalMargin); + out.writeInt(format); + out.writeInt(windowAnimations); + out.writeFloat(alpha); + out.writeFloat(dimAmount); + out.writeStrongBinder(token); + out.writeString(packageName); + TextUtils.writeToParcel(mTitle, out, parcelableFlags); + } + + public static final Parcelable.Creator<LayoutParams> CREATOR + = new Parcelable.Creator<LayoutParams>() { + public LayoutParams createFromParcel(Parcel in) { + return new LayoutParams(in); + } + + public LayoutParams[] newArray(int size) { + return new LayoutParams[size]; + } + }; + + + public LayoutParams(Parcel in) { + width = in.readInt(); + height = in.readInt(); + x = in.readInt(); + y = in.readInt(); + type = in.readInt(); + memoryType = in.readInt(); + flags = in.readInt(); + gravity = in.readInt(); + horizontalMargin = in.readFloat(); + verticalMargin = in.readFloat(); + format = in.readInt(); + windowAnimations = in.readInt(); + alpha = in.readFloat(); + dimAmount = in.readFloat(); + token = in.readStrongBinder(); + packageName = in.readString(); + mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + } + + public static final int LAYOUT_CHANGED = 1<<0; + public static final int TYPE_CHANGED = 1<<1; + public static final int FLAGS_CHANGED = 1<<2; + public static final int FORMAT_CHANGED = 1<<3; + public static final int ANIMATION_CHANGED = 1<<4; + public static final int DIM_AMOUNT_CHANGED = 1<<5; + public static final int TITLE_CHANGED = 1<<6; + public static final int ALPHA_CHANGED = 1<<7; + public static final int MEMORY_TYPE_CHANGED = 1<<8; + + public final int copyFrom(LayoutParams o) { + int changes = 0; + + if (width != o.width) { + width = o.width; + changes |= LAYOUT_CHANGED; + } + if (height != o.height) { + height = o.height; + changes |= LAYOUT_CHANGED; + } + if (x != o.x) { + x = o.x; + changes |= LAYOUT_CHANGED; + } + if (y != o.y) { + y = o.y; + changes |= LAYOUT_CHANGED; + } + if (type != o.type) { + type = o.type; + changes |= TYPE_CHANGED; + } + if (memoryType != o.memoryType) { + memoryType = o.memoryType; + changes |= MEMORY_TYPE_CHANGED; + } + if (flags != o.flags) { + flags = o.flags; + changes |= FLAGS_CHANGED; + } + if (gravity != o.gravity) { + gravity = o.gravity; + changes |= LAYOUT_CHANGED; + } + if (horizontalMargin != o.horizontalMargin) { + horizontalMargin = o.horizontalMargin; + changes |= LAYOUT_CHANGED; + } + if (verticalMargin != o.verticalMargin) { + verticalMargin = o.verticalMargin; + changes |= LAYOUT_CHANGED; + } + if (format != o.format) { + format = o.format; + changes |= FORMAT_CHANGED; + } + if (windowAnimations != o.windowAnimations) { + windowAnimations = o.windowAnimations; + changes |= ANIMATION_CHANGED; + } + if (token == null) { + // NOTE: token only copied if the recipient doesn't + // already have one. + token = o.token; + } + if (packageName == null) { + // NOTE: packageName only copied if the recipient doesn't + // already have one. + packageName = o.packageName; + } + if (!mTitle.equals(o.mTitle)) { + mTitle = o.mTitle; + changes |= TITLE_CHANGED; + } + if (alpha != o.alpha) { + alpha = o.alpha; + changes |= ALPHA_CHANGED; + } + if (dimAmount != o.dimAmount) { + dimAmount = o.dimAmount; + changes |= DIM_AMOUNT_CHANGED; + } + + return changes; + } + + @Override + public String debug(String output) { + output += "Contents of " + this + ":"; + Log.d("Debug", output); + output = super.debug(""); + Log.d("Debug", output); + Log.d("Debug", ""); + Log.d("Debug", "WindowManager.LayoutParams={title=" + mTitle + "}"); + return ""; + } + + @Override + public String toString() { + return "WM.LayoutParams{" + + Integer.toHexString(System.identityHashCode(this)) + + " (" + x + "," + y + ")(" + + (width==FILL_PARENT?"fill_parent":(width==WRAP_CONTENT?"wrap_content":width)) + + "x" + + (height==FILL_PARENT?"fill_parent":(height==WRAP_CONTENT?"wrap_content":height)) + + ") gr=#" + Integer.toHexString(gravity) + + " ty=" + type + " fl=#" + Integer.toHexString(flags) + + " fmt=" + format + "}"; + } + + private CharSequence mTitle = ""; + } +} diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java new file mode 100644 index 0000000..fbecf46 --- /dev/null +++ b/core/java/android/view/WindowManagerImpl.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2006 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.view; + +import android.graphics.PixelFormat; +import android.os.IBinder; +import android.util.AndroidRuntimeException; +import android.util.Config; +import android.util.Log; +import android.view.WindowManager; + +final class WindowLeaked extends AndroidRuntimeException { + public WindowLeaked(String msg) { + super(msg); + } +} + +/** + * Low-level communication with the global system window manager. It implements + * the ViewManager interface, allowing you to add any View subclass as a + * top-level window on the screen. Additional window manager specific layout + * parameters are defined for control over how windows are displayed. + * It also implemens the WindowManager interface, allowing you to control the + * displays attached to the device. + * + * <p>Applications will not normally use WindowManager directly, instead relying + * on the higher-level facilities in {@link android.app.Activity} and + * {@link android.app.Dialog}. + * + * <p>Even for low-level window manager access, it is almost never correct to use + * this class. For example, {@link android.app.Activity#getWindowManager} + * provides a ViewManager for adding windows that are associated with that + * activity -- the window manager will not normally allow you to add arbitrary + * windows that are not associated with an activity. + * + * @hide + */ +public class WindowManagerImpl implements WindowManager { + /** + * The user is navigating with keys (not the touch screen), so + * navigational focus should be shown. + */ + public static final int RELAYOUT_IN_TOUCH_MODE = 0x1; + /** + * This is the first time the window is being drawn, + * so the client must call drawingFinished() when done + */ + public static final int RELAYOUT_FIRST_TIME = 0x2; + + public static final int ADD_FLAG_APP_VISIBLE = 0x2; + public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_IN_TOUCH_MODE; + + public static final int ADD_OKAY = 0; + public static final int ADD_BAD_APP_TOKEN = -1; + public static final int ADD_BAD_SUBWINDOW_TOKEN = -2; + public static final int ADD_NOT_APP_TOKEN = -3; + public static final int ADD_APP_EXITING = -4; + public static final int ADD_DUPLICATE_ADD = -5; + public static final int ADD_STARTING_NOT_NEEDED = -6; + public static final int ADD_MULTIPLE_SINGLETON = -7; + public static final int ADD_PERMISSION_DENIED = -8; + + public static WindowManagerImpl getDefault() + { + return mWindowManager; + } + + public void addView(View view) + { + addView(view, new WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE)); + } + + public void addView(View view, ViewGroup.LayoutParams params) + { + addView(view, params, false); + } + + public void addViewNesting(View view, ViewGroup.LayoutParams params) + { + addView(view, params, false); + } + + private void addView(View view, ViewGroup.LayoutParams params, boolean nest) + { + if (Config.LOGV) Log.v("WindowManager", "addView view=" + view); + + if (!(params instanceof WindowManager.LayoutParams)) { + throw new IllegalArgumentException( + "Params must be WindowManager.LayoutParams"); + } + + final WindowManager.LayoutParams wparams + = (WindowManager.LayoutParams)params; + + ViewRoot root; + View panelParentView = null; + + synchronized (this) { + // Here's an odd/questionable case: if someone tries to add a + // view multiple times, then we simply bump up a nesting count + // and they need to remove the view the corresponding number of + // times to have it actually removed from the window manager. + // This is useful specifically for the notification manager, + // which can continually add/remove the same view as a + // notification gets updated. + int index = findViewLocked(view, false); + if (index >= 0) { + if (!nest) { + throw new IllegalStateException("View " + view + + " has already been added to the window manager."); + } + root = mRoots[index]; + root.mAddNesting++; + // Update layout parameters. + view.setLayoutParams(wparams); + root.setLayoutParams(wparams); + return; + } + + // If this is a panel window, then find the window it is being + // attached to for future reference. + if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && + wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + final int count = mViews != null ? mViews.length : 0; + for (int i=0; i<count; i++) { + if (mRoots[i].mWindow.asBinder() == wparams.token) { + panelParentView = mViews[i]; + } + } + } + + root = new ViewRoot(); + root.mAddNesting = 1; + + view.setLayoutParams(wparams); + + if (mViews == null) { + index = 1; + mViews = new View[1]; + mRoots = new ViewRoot[1]; + mParams = new WindowManager.LayoutParams[1]; + } else { + index = mViews.length + 1; + Object[] old = mViews; + mViews = new View[index]; + System.arraycopy(old, 0, mViews, 0, index-1); + old = mRoots; + mRoots = new ViewRoot[index]; + System.arraycopy(old, 0, mRoots, 0, index-1); + old = mParams; + mParams = new WindowManager.LayoutParams[index]; + System.arraycopy(old, 0, mParams, 0, index-1); + } + index--; + + mViews[index] = view; + mRoots[index] = root; + mParams[index] = wparams; + } + + // do this last because it fires off messages to start doing things + root.setView(view, wparams, panelParentView); + } + + public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + if (!(params instanceof WindowManager.LayoutParams)) { + throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); + } + + final WindowManager.LayoutParams wparams + = (WindowManager.LayoutParams)params; + + view.setLayoutParams(wparams); + + synchronized (this) { + int index = findViewLocked(view, true); + ViewRoot root = mRoots[index]; + mParams[index] = wparams; + root.setLayoutParams(wparams); + } + } + + public void removeView(View view) { + synchronized (this) { + int index = findViewLocked(view, true); + View curView = removeViewLocked(index); + if (curView == view) { + return; + } + + throw new IllegalStateException("Calling with view " + view + + " but the ViewRoot is attached to " + curView); + } + } + + public void removeViewImmediate(View view) { + synchronized (this) { + int index = findViewLocked(view, true); + ViewRoot root = mRoots[index]; + View curView = root.getView(); + + root.mAddNesting = 0; + root.die(true); + finishRemoveViewLocked(curView, index); + if (curView == view) { + return; + } + + throw new IllegalStateException("Calling with view " + view + + " but the ViewRoot is attached to " + curView); + } + } + + View removeViewLocked(int index) { + ViewRoot root = mRoots[index]; + View view = root.getView(); + + // Don't really remove until we have matched all calls to add(). + root.mAddNesting--; + if (root.mAddNesting > 0) { + return view; + } + + root.die(false); + finishRemoveViewLocked(view, index); + return view; + } + + void finishRemoveViewLocked(View view, int index) { + final int count = mViews.length; + + // remove it from the list + View[] tmpViews = new View[count-1]; + removeItem(tmpViews, mViews, index); + mViews = tmpViews; + + ViewRoot[] tmpRoots = new ViewRoot[count-1]; + removeItem(tmpRoots, mRoots, index); + mRoots = tmpRoots; + + WindowManager.LayoutParams[] tmpParams + = new WindowManager.LayoutParams[count-1]; + removeItem(tmpParams, mParams, index); + mParams = tmpParams; + + view.assignParent(null); + // func doesn't allow null... does it matter if we clear them? + //view.setLayoutParams(null); + } + + public void closeAll(IBinder token, String who, String what) { + synchronized (this) { + if (mViews == null) + return; + + int count = mViews.length; + //Log.i("foo", "Closing all windows of " + token); + for (int i=0; i<count; i++) { + //Log.i("foo", "@ " + i + " token " + mParams[i].token + // + " view " + mRoots[i].getView()); + if (token == null || mParams[i].token == token) { + ViewRoot root = mRoots[i]; + root.mAddNesting = 1; + + //Log.i("foo", "Force closing " + root); + if (who != null) { + WindowLeaked leak = new WindowLeaked( + what + " " + who + " has leaked window " + + root.getView() + " that was originally added here"); + leak.setStackTrace(root.getLocation().getStackTrace()); + Log.e("WindowManager", leak.getMessage(), leak); + } + + removeViewLocked(i); + i--; + count--; + } + } + } + } + + public void closeAll() { + closeAll(null, null, null); + } + + public Display getDefaultDisplay() { + return new Display(Display.DEFAULT_DISPLAY); + } + + private View[] mViews; + private ViewRoot[] mRoots; + private WindowManager.LayoutParams[] mParams; + + private static void removeItem(Object[] dst, Object[] src, int index) + { + if (dst.length > 0) { + if (index > 0) { + System.arraycopy(src, 0, dst, 0, index); + } + if (index < dst.length) { + System.arraycopy(src, index+1, dst, index, src.length-index-1); + } + } + } + + private int findViewLocked(View view, boolean required) + { + synchronized (this) { + final int count = mViews != null ? mViews.length : 0; + for (int i=0; i<count; i++) { + if (mViews[i] == view) { + return i; + } + } + if (required) { + throw new IllegalArgumentException( + "View not attached to window manager"); + } + return -1; + } + } + + private static WindowManagerImpl mWindowManager = new WindowManagerImpl(); +} diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java new file mode 100644 index 0000000..93e1a0b --- /dev/null +++ b/core/java/android/view/WindowManagerPolicy.java @@ -0,0 +1,717 @@ +/* + * Copyright (C) 2006 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.view; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.LocalPowerManager; + +/** + * This interface supplies all UI-specific behavior of the window manager. An + * instance of it is created by the window manager when it starts up, and allows + * customization of window layering, special window types, key dispatching, and + * layout. + * + * <p>Because this provides deep interaction with the system window manager, + * specific methods on this interface can be called from a variety of contexts + * with various restrictions on what they can do. These are encoded through + * a suffixes at the end of a method encoding the thread the method is called + * from and any locks that are held when it is being called; if no suffix + * is attached to a method, then it is not called with any locks and may be + * called from the main window manager thread or another thread calling into + * the window manager. + * + * <p>The current suffixes are: + * + * <dl> + * <dt> Ti <dd> Called from the input thread. This is the thread that + * collects pending input events and dispatches them to the appropriate window. + * It may block waiting for events to be processed, so that the input stream is + * properly serialized. + * <dt> Tq <dd> Called from the low-level input queue thread. This is the + * thread that reads events out of the raw input devices and places them + * into the global input queue that is read by the <var>Ti</var> thread. + * This thread should not block for a long period of time on anything but the + * key driver. + * <dt> Lw <dd> Called with the main window manager lock held. Because the + * window manager is a very low-level system service, there are few other + * system services you can call with this lock held. It is explicitly okay to + * make calls into the package manager and power manager; it is explicitly not + * okay to make calls into the activity manager. Note that + * {@link android.content.Context#checkPermission(String, int, int)} and + * variations require calling into the activity manager. + * <dt> Li <dd> Called with the input thread lock held. This lock can be + * acquired by the window manager while it holds the window lock, so this is + * even more restrictive than <var>Lw</var>. + * </dl> + * + * @hide + */ +public interface WindowManagerPolicy { + public final static int FLAG_WAKE = 0x00000001; + public final static int FLAG_WAKE_DROPPED = 0x00000002; + public final static int FLAG_SHIFT = 0x00000004; + public final static int FLAG_CAPS_LOCK = 0x00000008; + public final static int FLAG_ALT = 0x00000010; + public final static int FLAG_ALT_GR = 0x00000020; + public final static int FLAG_MENU = 0x00000040; + public final static int FLAG_LAUNCHER = 0x00000080; + + public final static int FLAG_WOKE_HERE = 0x10000000; + public final static int FLAG_BRIGHT_HERE = 0x20000000; + + public final static boolean WATCH_POINTER = false; + + // flags for interceptKeyTq + /** + * Pass this event to the user / app. To be returned from {@link #interceptKeyTq}. + */ + public final static int ACTION_PASS_TO_USER = 0x00000001; + + /** + * This key event should extend the user activity timeout and turn the lights on. + * To be returned from {@link #interceptKeyTq}. Do not return this and + * {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}. + */ + public final static int ACTION_POKE_USER_ACTIVITY = 0x00000002; + + /** + * This key event should put the device to sleep (and engage keyguard if necessary) + * To be returned from {@link #interceptKeyTq}. Do not return this and + * {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. + */ + public final static int ACTION_GO_TO_SLEEP = 0x00000004; + + /** + * Interface to the Window Manager state associated with a particular + * window. You can hold on to an instance of this interface from the call + * to prepareAddWindow() until removeWindow(). + */ + public interface WindowState { + /** + * Perform standard frame computation. The result can be obtained with + * getFrame() if so desired. Must be called with the window manager + * lock held. + * + * @param parentLeft The left edge of the parent container this window + * is in, used for computing its position. + * @param parentTop The top edge of the parent container this window + * is in, used for computing its position. + * @param parentRight The right edge of the parent container this window + * is in, used for computing its position. + * @param parentBottom The bottom edge of the parent container this window + * is in, used for computing its position. + * @param displayLeft The left edge of the available display, used + * for constraining the overall dimensions of the window. + * @param displayTop The left edge of the available display, used + * for constraining the overall dimensions of the window. + * @param displayRight The right edge of the available display, used + * for constraining the overall dimensions of the window. + * @param displayBottom The bottom edge of the available display, used + * for constraining the overall dimensions of the window. + */ + public void computeFrameLw(int parentLeft, int parentRight, int parentBottom, + int parentHeight, int displayLeft, int displayTop, + int displayRight, int displayBottom); + + /** + * Set the window's frame to an exact value. Must be called with the + * window manager lock held. + * + * @param left Left edge of the window. + * @param top Top edge of the window. + * @param right Right edge (exclusive) of the window. + * @param bottom Bottom edge (exclusive) of the window. + */ + public void setFrameLw(int left, int top, int right, int bottom); + + /** + * Retrieve the current frame of the window. Must be called with the + * window manager lock held. + * + * @return Rect The rectangle holding the window frame. + */ + public Rect getFrameLw(); + + /** + * Retrieve the current LayoutParams of the window. + * + * @return WindowManager.LayoutParams The window's internal LayoutParams + * instance. + */ + public WindowManager.LayoutParams getAttrs(); + + /** + * Return the token for the application (actually activity) that owns + * this window. May return null for system windows. + * + * @return An IApplicationToken identifying the owning activity. + */ + public IApplicationToken getAppToken(); + + /** + * Return true if, at any point, the application token associated with + * this window has actually displayed any windows. This is most useful + * with the "starting up" window to determine if any windows were + * displayed when it is closed. + * + * @return Returns true if one or more windows have been displayed, + * else false. + */ + public boolean hasAppShownWindows(); + + /** + * Return true if the application token has been asked to display an + * app starting icon as the application is starting up. + * + * @return Returns true if setAppStartingIcon() was called for this + * window's token. + */ + public boolean hasAppStartingIcon(); + + /** + * Return the Window that is being displayed as this window's + * application token is being started. + * + * @return Returns the currently displayed starting window, or null if + * it was not requested, has not yet been displayed, or has + * been removed. + */ + public WindowState getAppStartingWindow(); + + /** + * Is this window currently visible to the user on-screen? It is + * displayed either if it is visible or it is currently running an + * animation before no longer being visible. Must be called with the + * window manager lock held. + */ + boolean isDisplayedLw(); + + /** + * Returns true if the window is both full screen and opaque. Must be + * called with the window manager lock held. + * + * @param width The width of the screen + * @param height The height of the screen + * @param shownFrame If true, this is based on the actual shown frame of + * the window (taking into account animations); if + * false, this is based on the currently requested + * frame, which any current animation will be moving + * towards. + * @return Returns true if the window is both full screen and opaque + */ + public boolean fillsScreenLw(int width, int height, boolean shownFrame); + + /** + * Returns true if this window has been shown on screen at some time in + * the past. Must be called with the window manager lock held. + * + * @return boolean + */ + public boolean hasDrawnLw(); + + /** + * Can be called by the policy to force a window to be hidden, + * regardless of whether the client or window manager would like + * it shown. Must be called with the window manager lock held. + */ + public void hideLw(); + + /** + * Can be called to undo the effect of {@link #hideLw}, allowing a + * window to be shown as long as the window manager and client would + * also like it to be shown. Must be called with the window manager + * lock held. + */ + public void showLw(); + + /** + * Sets insets on the window that represent the area within the window that is covered + * by system windows (e.g. status bar). Must be called with the window + * manager lock held. + * + * @param l + * @param t + * @param r + * @param b + */ + public void setCoveredInsetsLw(int l, int t, int r, int b); + } + + /** No transition happening. */ + public final int TRANSIT_NONE = 0; + /** Window has been added to the screen. */ + public final int TRANSIT_ENTER = 1; + /** Window has been removed from the screen. */ + public final int TRANSIT_EXIT = 2; + /** Window has been made visible. */ + public final int TRANSIT_SHOW = 3; + /** Window has been made invisible. */ + public final int TRANSIT_HIDE = 4; + /** The "application starting" preview window is no longer needed, and will + * animate away to show the real window. */ + public final int TRANSIT_PREVIEW_DONE = 5; + /** A window in a new activity is being opened on top of an existing one + * in the same task. */ + public final int TRANSIT_ACTIVITY_OPEN = 6; + /** The window in the top-most activity is being closed to reveal the + * previous activity in the same task. */ + public final int TRANSIT_ACTIVITY_CLOSE = 7; + /** A window in a new task is being opened on top of an existing one + * in another activity's task. */ + public final int TRANSIT_TASK_OPEN = 8; + /** A window in the top-most activity is being closed to reveal the + * previous activity in a different task. */ + public final int TRANSIT_TASK_CLOSE = 9; + /** A window in an existing task is being displayed on top of an existing one + * in another activity's task. */ + public final int TRANSIT_TASK_TO_FRONT = 10; + /** A window in an existing task is being put below all other tasks. */ + public final int TRANSIT_TASK_TO_BACK = 11; + + /** Screen turned off because of power button */ + public final int OFF_BECAUSE_OF_USER = 1; + /** Screen turned off because of timeout */ + public final int OFF_BECAUSE_OF_TIMEOUT = 2; + + /** + * Magic constant to {@link IWindowManager#setRotation} to not actually + * modify the rotation. + */ + public final int USE_LAST_ROTATION = -1000; + + /** + * Perform initialization of the policy. + * + * @param context The system context we are running in. + * @param powerManager + */ + public void init(Context context, IWindowManager windowManager, + LocalPowerManager powerManager); + + /** + * Check permissions when adding a window. + * + * @param attrs The window's LayoutParams. + * + * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed; + * else an error code, usually + * {@link WindowManagerImpl#ADD_PERMISSION_DENIED}, to abort the add. + */ + public int checkAddPermission(WindowManager.LayoutParams attrs); + + /** + * Sanitize the layout parameters coming from a client. Allows the policy + * to do things like ensure that windows of a specific type can't take + * input focus. + * + * @param attrs The window layout parameters to be modified. These values + * are modified in-place. + */ + public void adjustWindowParamsLw(WindowManager.LayoutParams attrs); + + /** + * After the window manager has computed the current configuration based + * on its knowledge of the display and input devices, it gives the policy + * a chance to adjust the information contained in it. If you want to + * leave it as-is, simply do nothing. + * + * <p>This method may be called by any thread in the window manager, but + * no internal locks in the window manager will be held. + * + * @param config The Configuration being computed, for you to change as + * desired. + */ + public void adjustConfigurationLw(Configuration config); + + /** + * Assign a window type to a layer. Allows you to control how different + * kinds of windows are ordered on-screen. + * + * @param type The type of window being assigned. + * + * @return int An arbitrary integer used to order windows, with lower + * numbers below higher ones. + */ + public int windowTypeToLayerLw(int type); + + /** + * Return how to Z-order sub-windows in relation to the window they are + * attached to. Return positive to have them ordered in front, negative for + * behind. + * + * @param type The sub-window type code. + * + * @return int Layer in relation to the attached window, where positive is + * above and negative is below. + */ + public int subWindowTypeToLayerLw(int type); + + /** + * Called when the system would like to show a UI to indicate that an + * application is starting. You can use this to add a + * APPLICATION_STARTING_TYPE window with the given appToken to the window + * manager (using the normal window manager APIs) that will be shown until + * the application displays its own window. This is called without the + * window manager locked so that you can call back into it. + * + * @param appToken Token of the application being started. + * @param packageName The name of the application package being started. + * @param theme Resource defining the application's overall visual theme. + * @param nonLocalizedLabel The default title label of the application if + * no data is found in the resource. + * @param labelRes The resource ID the application would like to use as its name. + * @param icon The resource ID the application would like to use as its icon. + * + * @return Optionally you can return the View that was used to create the + * window, for easy removal in removeStartingWindow. + * + * @see #removeStartingWindow + */ + public View addStartingWindow(IBinder appToken, String packageName, + int theme, CharSequence nonLocalizedLabel, + int labelRes, int icon); + + /** + * Called when the first window of an application has been displayed, while + * {@link #addStartingWindow} has created a temporary initial window for + * that application. You should at this point remove the window from the + * window manager. This is called without the window manager locked so + * that you can call back into it. + * + * <p>Note: due to the nature of these functions not being called with the + * window manager locked, you must be prepared for this function to be + * called multiple times and/or an initial time with a null View window + * even if you previously returned one. + * + * @param appToken Token of the application that has started. + * @param window Window View that was returned by createStartingWindow. + * + * @see #addStartingWindow + */ + public void removeStartingWindow(IBinder appToken, View window); + + /** + * Prepare for a window being added to the window manager. You can throw an + * exception here to prevent the window being added, or do whatever setup + * you need to keep track of the window. + * + * @param win The window being added. + * @param attrs The window's LayoutParams. + * + * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed, else an + * error code to abort the add. + */ + public int prepareAddWindowLw(WindowState win, + WindowManager.LayoutParams attrs); + + /** + * Called when a window is being removed from a window manager. Must not + * throw an exception -- clean up as much as possible. + * + * @param win The window being removed. + */ + public void removeWindowLw(WindowState win); + + /** + * Control the animation to run when a window's state changes. Return a + * non-0 number to force the animation to a specific resource ID, or 0 + * to use the default animation. + * + * @param win The window that is changing. + * @param transit What is happening to the window: {@link #TRANSIT_ENTER}, + * {@link #TRANSIT_EXIT}, {@link #TRANSIT_SHOW}, or + * {@link #TRANSIT_HIDE}. + * + * @return Resource ID of the actual animation to use, or 0 for none. + */ + public int selectAnimationLw(WindowState win, int transit); + + /** + * Called from the key queue thread before a key is dispatched to the + * input thread. + * + * <p>There are some actions that need to be handled here because they + * affect the power state of the device, for example, the power keys. + * Generally, it's best to keep as little as possible in the queue thread + * because it's the most fragile. + * + * @param event the raw input event as read from the driver + * @param screenIsOn true if the screen is already on + * @return The bitwise or of the {@link #ACTION_PASS_TO_USER}, + * {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags. + */ + public int interceptKeyTq(RawInputEvent event, boolean screenIsOn); + + /** + * Called from the input thread before a key is dispatched to a window. + * + * <p>Allows you to define + * behavior for keys that can not be overridden by applications or redirect + * key events to a different window. This method is called from the + * input thread, with no locks held. + * + * <p>Note that if you change the window a key is dispatched to, the new + * target window will receive the key event without having input focus. + * + * @param win The window that currently has focus. This is where the key + * event will normally go. + * @param code Key code. + * @param metaKeys TODO + * @param down Is this a key press (true) or release (false)? + * @param repeatCount Number of times a key down has repeated. + * @return Returns true if the policy consumed the event and it should + * not be further dispatched. + */ + public boolean interceptKeyTi(WindowState win, int code, + int metaKeys, boolean down, int repeatCount); + + /** + * Called when layout of the windows is about to start. + * + * @param displayWidth The current full width of the screen. + * @param displayHeight The current full height of the screen. + */ + public void beginLayoutLw(int displayWidth, int displayHeight); + + /** + * Called for each window attached to the window manager as layout is + * proceeding. The implementation of this function must take care of + * setting the window's frame, either here or in finishLayout(). + * + * @param win The window being positioned. + * @param attrs The LayoutParams of the window. + * @param attached For sub-windows, the window it is attached to; this + * window will already have had layoutWindow() called on it + * so you can use its Rect. Otherwise null. + */ + public void layoutWindowLw(WindowState win, + WindowManager.LayoutParams attrs, WindowState attached); + + + /** + * Return the insets for the areas covered by system windows. These values are computed on the + * mose recent layout, so they are not guaranteed to be correct. + * + * @param attrs The LayoutParams of the window. + * @param coveredInset The areas covered by system windows, expressed as positive insets + * + */ + public void getCoveredInsetHintLw(WindowManager.LayoutParams attrs, Rect coveredInset); + + /** + * Called when layout of the windows is finished. After this function has + * returned, all windows given to layoutWindow() <em>must</em> have had a + * frame assigned. + */ + public void finishLayoutLw(); + + /** + * Called when animation of the windows is about to start. + * + * @param displayWidth The current full width of the screen. + * @param displayHeight The current full height of the screen. + */ + public void beginAnimationLw(int displayWidth, int displayHeight); + + /** + * Called each time a window is animating. + * + * @param win The window being positioned. + * @param attrs The LayoutParams of the window. + */ + public void animatingWindowLw(WindowState win, + WindowManager.LayoutParams attrs); + + /** + * Called when animation of the windows is finished. If in this function you do + * something that may have modified the animation state of another window, + * be sure to return true in order to perform another animation frame. + * + * @return Return true if animation state may have changed (so that another + * frame of animation will be run). + */ + public boolean finishAnimationLw(); + + /** + * Called after the screen turns off. + * + * @param why {@link #OFF_BECAUSE_OF_USER} or + * {@link #OFF_BECAUSE_OF_TIMEOUT}. + */ + public void screenTurnedOff(int why); + + /** + * Called after the screen turns on. + */ + public void screenTurnedOn(); + + /** + * Perform any initial processing of a low-level input event before the + * window manager handles special keys and generates a high-level event + * that is dispatched to the application. + * + * @param event The input event that has occurred. + * + * @return Return true if you have consumed the event and do not want + * further processing to occur; return false for normal processing. + */ + public boolean preprocessInputEventTq(RawInputEvent event); + + /** + * Determine whether a given key code is used to cause an app switch + * to occur (most often the HOME key, also often ENDCALL). If you return + * true, then the system will go into a special key processing state + * where it drops any pending events that it cans and adjusts timeouts to + * try to get to this key as quickly as possible. + * + * <p>Note that this function is called from the low-level input queue + * thread, with either/or the window or input lock held; be very careful + * about what you do here. You absolutely should never acquire a lock + * that you would ever hold elsewhere while calling out into the window + * manager or view hierarchy. + * + * @param keycode The key that should be checked for performing an + * app switch before delivering to the application. + * + * @return Return true if this is an app switch key and special processing + * should happen; return false for normal processing. + */ + public boolean isAppSwitchKeyTqTiLwLi(int keycode); + + /** + * Determine whether a given key code is used for movement within a UI, + * and does not generally cause actions to be performed (normally the DPAD + * movement keys, NOT the DPAD center press key). This is called + * when {@link #isAppSwitchKeyTiLi} returns true to remove any pending events + * in the key queue that are not needed to switch applications. + * + * <p>Note that this function is called from the low-level input queue + * thread; be very careful about what you do here. + * + * @param keycode The key that is waiting to be delivered to the + * application. + * + * @return Return true if this is a purely navigation key and can be + * dropped without negative consequences; return false to keep it. + */ + public boolean isMovementKeyTi(int keycode); + + /** + * Given the current state of the world, should this relative movement + * wake up the device? + * + * @param device The device the movement came from. + * @param classes The input classes associated with the device. + * @param event The input event that occurred. + * @return + */ + public boolean isWakeRelMovementTq(int device, int classes, + RawInputEvent event); + + /** + * Given the current state of the world, should this absolute movement + * wake up the device? + * + * @param device The device the movement came from. + * @param classes The input classes associated with the device. + * @param event The input event that occurred. + * @return + */ + public boolean isWakeAbsMovementTq(int device, int classes, + RawInputEvent event); + + /** + * Tell the policy if anyone is requesting that keyguard not come on. + * + * @param enabled Whether keyguard can be on or not. does not actually + * turn it on, unless it was previously disabled with this function. + * + * @see android.app.KeyguardManager.KeyguardLock#disableKeyguard() + * @see android.app.KeyguardManager.KeyguardLock#reenableKeyguard() + */ + public void enableKeyguard(boolean enabled); + + /** + * Callback used by {@link WindowManagerPolicy#exitKeyguardSecurely} + */ + interface OnKeyguardExitResult { + void onKeyguardExitResult(boolean success); + } + + /** + * Tell the policy if anyone is requesting the keyguard to exit securely + * (this would be called after the keyguard was disabled) + * @param callback Callback to send the result back. + * @see android.app.KeyguardManager#exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult) + */ + void exitKeyguardSecurely(OnKeyguardExitResult callback); + + /** + * Return if keyguard is currently showing. + */ + public boolean keyguardIsShowingTq(); + + /** + * inKeyguardRestrictedKeyInputMode + * + * if keyguard screen is showing or in restricted key input mode (i.e. in + * keyguard password emergency screen). When in such mode, certain keys, + * such as the Home key and the right soft keys, don't work. + * + * @return true if in keyguard restricted input mode. + */ + public boolean inKeyguardRestrictedKeyInputMode(); + + /** + * Given an orientation constant + * ({@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE + * ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE} or + * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT + * ActivityInfo.SCREEN_ORIENTATION_PORTRAIT}), return a surface + * rotation. + */ + public int rotationForOrientation(int orientation); + + /** + * Called when the system is mostly done booting + */ + public void systemReady(); + + /** + * Called when we have finished booting and can now display the home + * screen to the user. This wilWl happen after systemReady(), and at + * this point the display is active. + */ + public void enableScreenAfterBoot(); + + /** + * Returns true if the user's cheek has been pressed against the phone. This is + * determined by comparing the event's size attribute with a threshold value. + * For example for a motion event like down or up or move, if the size exceeds + * the threshold, it is considered as cheek press. + * @param ev the motion event generated when the cheek is pressed + * against the phone + * @return Returns true if the user's cheek has been pressed against the phone + * screen resulting in an invalid motion event + */ + public boolean isCheekPressedAgainstScreen(MotionEvent ev); + + public void setCurrentOrientation(int newOrientation); +} diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java new file mode 100644 index 0000000..fdb6f9d --- /dev/null +++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2007 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.view.animation; + +import android.content.Context; +import android.util.AttributeSet; + +/** + * An interpolator where the rate of change starts and ends slowly but + * accelerates through the middle. + * + */ +public class AccelerateDecelerateInterpolator implements Interpolator { + public AccelerateDecelerateInterpolator() { + } + + public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) { + } + + public float getInterpolation(float input) { + return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; + } +} diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java new file mode 100644 index 0000000..b9e293f --- /dev/null +++ b/core/java/android/view/animation/AccelerateInterpolator.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +/** + * An interpolator where the rate of change starts out slowly and + * and then accelerates. + * + */ +public class AccelerateInterpolator implements Interpolator { + public AccelerateInterpolator() { + } + + /** + * Constructor + * + * @param factor Degree to which the animation should be eased. Seting + * factor to 1.0f produces a y=x^2 parabola. Increasing factor above + * 1.0f exaggerates the ease-in effect (i.e., it starts even + * slower and ends evens faster) + */ + public AccelerateInterpolator(float factor) { + mFactor = factor; + } + + public AccelerateInterpolator(Context context, AttributeSet attrs) { + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator); + + mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f); + + a.recycle(); + } + + public float getInterpolation(float input) { + if (mFactor == 1.0f) { + return (float)(input * input); + } else { + return (float)Math.pow(input, 2 * mFactor); + } + } + + private float mFactor = 1.0f; +} diff --git a/core/java/android/view/animation/AlphaAnimation.java b/core/java/android/view/animation/AlphaAnimation.java new file mode 100644 index 0000000..16a10a4 --- /dev/null +++ b/core/java/android/view/animation/AlphaAnimation.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +/** + * An animation that controls the alpha level of an object. + * Useful for fading things in and out. This animation ends up + * changing the alpha property of a {@link Transformation} + * + */ +public class AlphaAnimation extends Animation { + private float mFromAlpha; + private float mToAlpha; + + /** + * Constructor used whan an AlphaAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public AlphaAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AlphaAnimation); + + mFromAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_fromAlpha, 1.0f); + mToAlpha = a.getFloat(com.android.internal.R.styleable.AlphaAnimation_toAlpha, 1.0f); + + a.recycle(); + } + + /** + * Constructor to use when building an AlphaAnimation from code + * + * @param fromAlpha Starting alpha value for the animation, where 1.0 means + * fully opaque and 0.0 means fully transparent. + * @param toAlpha Ending alpha value for the animation. + */ + public AlphaAnimation(float fromAlpha, float toAlpha) { + mFromAlpha = fromAlpha; + mToAlpha = toAlpha; + } + + /** + * Changes the alpha property of the supplied {@link Transformation} + */ + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + final float alpha = mFromAlpha; + t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime)); + } + + @Override + public boolean willChangeTransformationMatrix() { + return false; + } + + @Override + public boolean willChangeBounds() { + return false; + } +} diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java new file mode 100644 index 0000000..39fe561 --- /dev/null +++ b/core/java/android/view/animation/Animation.java @@ -0,0 +1,796 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.TypedValue; + +/** + * Abstraction for an Animation that can be applied to Views, Surfaces, or + * other objects. See the {@link android.view.animation animation package + * description file}. + */ +public abstract class Animation { + /** + * Repeat the animation indefinitely. + */ + public static final int INFINITE = -1; + + /** + * When the animation reaches the end and the repeat count is INFINTE_REPEAT + * or a positive value, the animation restarts from the beginning. + */ + public static final int RESTART = 1; + + /** + * When the animation reaches the end and the repeat count is INFINTE_REPEAT + * or a positive value, the animation plays backward (and then forward again). + */ + public static final int REVERSE = 2; + + /** + * Can be used as the start time to indicate the start time should be the current + * time when {@link #getTransformation(long, Transformation)} is invoked for the + * first animation frame. This can is useful for short animations. + */ + public static final int START_ON_FIRST_FRAME = -1; + + /** + * The specified dimension is an absolute number of pixels. + */ + public static final int ABSOLUTE = 0; + + /** + * The specified dimension holds a float and should be multiplied by the + * height or width of the object being animated. + */ + public static final int RELATIVE_TO_SELF = 1; + + /** + * The specified dimension holds a float and should be multiplied by the + * height or width of the parent of the object being animated. + */ + public static final int RELATIVE_TO_PARENT = 2; + + /** + * Requests that the content being animated be kept in its current Z + * order. + */ + public static final int ZORDER_NORMAL = 0; + + /** + * Requests that the content being animated be forced on top of all other + * content for the duration of the animation. + */ + public static final int ZORDER_TOP = 1; + + /** + * Requests that the content being animated be forced under all other + * content for the duration of the animation. + */ + public static final int ZORDER_BOTTOM = -1; + + /** + * Set by {@link #getTransformation(long, Transformation)} when the animation ends. + */ + boolean mEnded = false; + + /** + * Set by {@link #getTransformation(long, Transformation)} when the animation starts. + */ + boolean mStarted = false; + + /** + * Set by {@link #getTransformation(long, Transformation)} when the animation repeats + * in REVERSE mode. + */ + boolean mCycleFlip = false; + + /** + * This value must be set to true by {@link #initialize(int, int, int, int)}. It + * indicates the animation was successfully initialized and can be played. + */ + boolean mInitialized = false; + + /** + * Indicates whether the animation transformation should be applied before the + * animation starts. + */ + boolean mFillBefore = true; + + /** + * Indicates whether the animation transformation should be applied after the + * animation ends. + */ + boolean mFillAfter = false; + + /** + * The time in milliseconds at which the animation must start; + */ + long mStartTime = -1; + + /** + * The delay in milliseconds after which the animation must start. When the + * start offset is > 0, the start time of the animation is startTime + startOffset. + */ + long mStartOffset; + + /** + * The duration of one animation cycle in milliseconds. + */ + long mDuration; + + /** + * The number of times the animation must repeat. By default, an animation repeats + * indefinitely. + */ + int mRepeatCount = 0; + + /** + * Indicates how many times the animation was repeated. + */ + int mRepeated = 0; + + /** + * The behavior of the animation when it repeats. The repeat mode is either + * {@link #RESTART} or {@link #REVERSE}. + * + */ + int mRepeatMode = RESTART; + + /** + * The interpolator used by the animation to smooth the movement. + */ + Interpolator mInterpolator; + + /** + * The animation listener to be notified when the animation starts, ends or repeats. + */ + AnimationListener mListener; + + /** + * Desired Z order mode during animation. + */ + private int mZAdjustment; + + // Indicates what was the last value returned by getTransformation() + private boolean mMore = true; + + /** + * Creates a new animation with a duration of 0ms, the default interpolator, with + * fillBefore set to true and fillAfter set to false + */ + public Animation() { + ensureInterpolator(); + } + + /** + * Creates a new animation whose parameters come from the specified context and + * attributes set. + * + * @param context the application environment + * @param attrs the set of attributes holding the animation parameters + */ + public Animation(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animation); + + setDuration((long) a.getInt(com.android.internal.R.styleable.Animation_duration, 0)); + setStartOffset((long) a.getInt(com.android.internal.R.styleable.Animation_startOffset, 0)); + + setFillBefore(a.getBoolean(com.android.internal.R.styleable.Animation_fillBefore, mFillBefore)); + setFillAfter(a.getBoolean(com.android.internal.R.styleable.Animation_fillAfter, mFillAfter)); + + final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0); + if (resID > 0) { + setInterpolator(context, resID); + } + + setRepeatCount(a.getInt(com.android.internal.R.styleable.Animation_repeatCount, mRepeatCount)); + setRepeatMode(a.getInt(com.android.internal.R.styleable.Animation_repeatMode, RESTART)); + + setZAdjustment(a.getInt(com.android.internal.R.styleable.Animation_zAdjustment, ZORDER_NORMAL)); + + ensureInterpolator(); + + a.recycle(); + } + + /** + * Reset the initialization state of this animation. + * + * @see #initialize(int, int, int, int) + */ + public void reset() { + mInitialized = false; + mCycleFlip = false; + mRepeated = 0; + mMore = true; + } + + /** + * Whether or not the animation has been initialized. + * + * @return Has this animation been initialized. + * @see #initialize(int, int, int, int) + */ + public boolean isInitialized() { + return mInitialized; + } + + /** + * Initialize this animation with the dimensions of the object being + * animated as well as the objects parents. (This is to support animation + * sizes being specifed relative to these dimensions.) + * + * <p>Objects that interpret a Animations should call this method when + * the sizes of the object being animated and its parent are known, and + * before calling {@link #getTransformation}. + * + * + * @param width Width of the object being animated + * @param height Height of the object being animated + * @param parentWidth Width of the animated object's parent + * @param parentHeight Height of the animated object's parent + */ + public void initialize(int width, int height, int parentWidth, int parentHeight) { + mInitialized = true; + mCycleFlip = false; + mRepeated = 0; + mMore = true; + } + + /** + * Sets the acceleration curve for this animation. The interpolator is loaded as + * a resource from the specified context. + * + * @param context The application environment + * @param resID The resource identifier of the interpolator to load + * @attr ref android.R.styleable#Animation_interpolator + */ + public void setInterpolator(Context context, int resID) { + setInterpolator(AnimationUtils.loadInterpolator(context, resID)); + } + + /** + * Sets the acceleration curve for this animation. Defaults to a linear + * interpolation. + * + * @param i The interpolator which defines the acceleration curve + * @attr ref android.R.styleable#Animation_interpolator + */ + public void setInterpolator(Interpolator i) { + mInterpolator = i; + } + + /** + * When this animation should start relative to the start time. This is most + * useful when composing complex animations using an {@link AnimationSet } + * where some of the animations components start at different times. + * + * @param startOffset When this Animation should start, in milliseconds from + * the start time of the root AnimationSet. + * @attr ref android.R.styleable#Animation_startOffset + */ + public void setStartOffset(long startOffset) { + mStartOffset = startOffset; + } + + /** + * How long this animation should last. + * + * @param durationMillis Duration in milliseconds + * @attr ref android.R.styleable#Animation_duration + */ + public void setDuration(long durationMillis) { + mDuration = durationMillis; + } + + /** + * Ensure that the duration that this animation will run is not longer + * than <var>durationMillis</var>. In addition to adjusting the duration + * itself, this ensures that the repeat count also will not make it run + * longer than the given time. + * + * @param durationMillis The maximum duration the animation is allowed + * to run. + */ + public void restrictDuration(long durationMillis) { + if (mStartOffset > durationMillis) { + mStartOffset = durationMillis; + mDuration = 0; + mRepeatCount = 0; + return; + } + + long dur = mDuration + mStartOffset; + if (dur > durationMillis) { + mDuration = dur = durationMillis-mStartOffset; + } + if (mRepeatCount < 0 || mRepeatCount > durationMillis + || (dur*mRepeatCount) > durationMillis) { + mRepeatCount = (int)(durationMillis/dur); + } + } + + /** + * How much to scale the duration by. + * + * @param scale The amount to scale the duration. + */ + public void scaleCurrentDuration(float scale) { + mDuration = (long) (mDuration * scale); + } + + /** + * When this animation should start. When the start time is set to + * {@link #START_ON_FIRST_FRAME}, the animation will start the first time + * {@link #getTransformation(long, Transformation)} is invoked. The time passed + * to this method should be obtained by calling + * {@link AnimationUtils#currentAnimationTimeMillis()} instead of + * {@link System#currentTimeMillis()}. + * + * @param startTimeMillis the start time in milliseconds + */ + public void setStartTime(long startTimeMillis) { + mStartTime = startTimeMillis; + mStarted = mEnded = false; + mCycleFlip = false; + mRepeated = 0; + mMore = true; + } + + /** + * Convenience method to start the animation the first time + * {@link #getTransformation(long, Transformation)} is invoked. + */ + public void start() { + setStartTime(-1); + } + + /** + * Convenience method to start the animation at the current time in + * milliseconds. + */ + public void startNow() { + setStartTime(AnimationUtils.currentAnimationTimeMillis()); + } + + /** + * Defines what this animation should do when it reaches the end. This + * setting is applied only when the repeat count is either greater than + * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}. + * + * @param repeatMode {@link #RESTART} or {@link #REVERSE} + * @attr ref android.R.styleable#Animation_repeatMode + */ + public void setRepeatMode(int repeatMode) { + mRepeatMode = repeatMode; + } + + /** + * Sets how many times the animation should be repeated. If the repeat + * count is 0, the animation is never repeated. If the repeat count is + * greater than 0 or {@link #INFINITE}, the repeat mode will be taken + * into account. The repeat count if 0 by default. + * + * @param repeatCount the number of times the animation should be repeated + * @attr ref android.R.styleable#Animation_repeatCount + */ + public void setRepeatCount(int repeatCount) { + if (repeatCount < 0) { + repeatCount = INFINITE; + } + mRepeatCount = repeatCount; + } + + /** + * If fillBefore is true, this animation will apply its transformation + * before the start time of the animation. Defaults to false if not set. + * Note that this applies when using an {@link + * android.view.animation.AnimationSet AnimationSet} to chain + * animations. The transformation is not applied before the AnimationSet + * itself starts. + * + * @param fillBefore true if the animation should apply its transformation before it starts + * @attr ref android.R.styleable#Animation_fillBefore + */ + public void setFillBefore(boolean fillBefore) { + mFillBefore = fillBefore; + } + + /** + * If fillAfter is true, the transformation that this animation performed + * will persist when it is finished. Defaults to false if not set. + * Note that this applies when using an {@link + * android.view.animation.AnimationSet AnimationSet} to chain + * animations. The transformation is not applied before the AnimationSet + * itself starts. + * + * @param fillAfter true if the animation should apply its transformation after it ends + * @attr ref android.R.styleable#Animation_fillAfter + */ + public void setFillAfter(boolean fillAfter) { + mFillAfter = fillAfter; + } + + /** + * Set the Z ordering mode to use while running the animation. + * + * @param zAdjustment The desired mode, one of {@link #ZORDER_NORMAL}, + * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}. + * @attr ref android.R.styleable#Animation_zAdjustment + */ + public void setZAdjustment(int zAdjustment) { + mZAdjustment = zAdjustment; + } + + /** + * Gets the acceleration curve type for this animation. + * + * @return the {@link Interpolator} associated to this animation + * @attr ref android.R.styleable#Animation_interpolator + */ + public Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * When this animation should start. If the animation has not startet yet, + * this method might return {@link #START_ON_FIRST_FRAME}. + * + * @return the time in milliseconds when the animation should start or + * {@link #START_ON_FIRST_FRAME} + */ + public long getStartTime() { + return mStartTime; + } + + /** + * How long this animation should last + * + * @return the duration in milliseconds of the animation + * @attr ref android.R.styleable#Animation_duration + */ + public long getDuration() { + return mDuration; + } + + /** + * When this animation should start, relative to StartTime + * + * @return the start offset in milliseconds + * @attr ref android.R.styleable#Animation_startOffset + */ + public long getStartOffset() { + return mStartOffset; + } + + /** + * Defines what this animation should do when it reaches the end. + * + * @return either one of {@link #REVERSE} or {@link #RESTART} + * @attr ref android.R.styleable#Animation_repeatMode + */ + public int getRepeatMode() { + return mRepeatMode; + } + + /** + * Defines how many times the animation should repeat. The default value + * is 0. + * + * @return the number of times the animation should repeat, or {@link #INFINITE} + * @attr ref android.R.styleable#Animation_repeatCount + */ + public int getRepeatCount() { + return mRepeatCount; + } + + /** + * If fillBefore is true, this animation will apply its transformation + * before the start time of the animation. + * + * @return true if the animation applies its transformation before it starts + * @attr ref android.R.styleable#Animation_fillBefore + */ + public boolean getFillBefore() { + return mFillBefore; + } + + /** + * If fillAfter is true, this animation will apply its transformation + * after the end time of the animation. + * + * @return true if the animation applies its transformation after it ends + * @attr ref android.R.styleable#Animation_fillAfter + */ + public boolean getFillAfter() { + return mFillAfter; + } + + /** + * Returns the Z ordering mode to use while running the animation as + * previously set by {@link #setZAdjustment}. + * + * @return Returns one of {@link #ZORDER_NORMAL}, + * {@link #ZORDER_TOP}, or {@link #ZORDER_BOTTOM}. + * @attr ref android.R.styleable#Animation_zAdjustment + */ + public int getZAdjustment() { + return mZAdjustment; + } + + /** + * <p>Indicates whether or not this animation will affect the transformation + * matrix. For instance, a fade animation will not affect the matrix whereas + * a scale animation will.</p> + * + * @return true if this animation will change the transformation matrix + */ + public boolean willChangeTransformationMatrix() { + // assume we will change the matrix + return true; + } + + /** + * <p>Indicates whether or not this animation will affect the bounds of the + * animated view. For instance, a fade animation will not affect the bounds + * whereas a 200% scale animation will.</p> + * + * @return true if this animation will change the view's bounds + */ + public boolean willChangeBounds() { + // assume we will change the bounds + return true; + } + + /** + * <p>Binds an animation listener to this animation. The animation listener + * is notified of animation events such as the end of the animation or the + * repetition of the animation.</p> + * + * @param listener the animation listener to be notified + */ + public void setAnimationListener(AnimationListener listener) { + mListener = listener; + } + + /** + * Gurantees that this animation has an interpolator. Will use + * a AccelerateDecelerateInterpolator is nothing else was specified. + */ + protected void ensureInterpolator() { + if (mInterpolator == null) { + mInterpolator = new AccelerateDecelerateInterpolator(); + } + } + + /** + * Gets the transformation to apply at a specified point in time. Implementations of this + * method should always replace the specified Transformation or document they are doing + * otherwise. + * + * @param currentTime Where we are in the animation. This is wall clock time. + * @param outTransformation A tranformation object that is provided by the + * caller and will be filled in by the animation. + * @return True if the animation is still running + */ + public boolean getTransformation(long currentTime, Transformation outTransformation) { + if (mStartTime == -1) { + mStartTime = currentTime; + } + + final long startOffset = getStartOffset(); + float normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / + (float) mDuration; + + boolean expired = normalizedTime >= 1.0f; + // Pin time to 0.0 to 1.0 range + normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); + mMore = !expired; + + if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { + if (!mStarted) { + if (mListener != null) { + mListener.onAnimationStart(this); + } + mStarted = true; + } + + if (mCycleFlip) { + normalizedTime = 1.0f - normalizedTime; + } + + final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); + applyTransformation(interpolatedTime, outTransformation); + } + + if (expired) { + if (mRepeatCount == mRepeated) { + if (!mEnded) { + if (mListener != null) { + mListener.onAnimationEnd(this); + } + mEnded = true; + } + } else { + if (mRepeatCount > 0) { + mRepeated++; + } + + if (mRepeatMode == REVERSE) { + mCycleFlip = !mCycleFlip; + } + + mStartTime = -1; + mMore = true; + + if (mListener != null) { + mListener.onAnimationRepeat(this); + } + } + } + + return mMore; + } + + /** + * <p>Indicates whether this animation has started or not.</p> + * + * @return true if the animation has started, false otherwise + */ + public boolean hasStarted() { + return mStarted; + } + + /** + * <p>Indicates whether this animation has ended or not.</p> + * + * @return true if the animation has ended, false otherwise + */ + public boolean hasEnded() { + return mEnded; + } + + /** + * Helper for getTransformation. Subclasses should implement this to apply + * their transforms given an interpolation value. Implementations of this + * method should always replace the specified Transformation or document + * they are doing otherwise. + * + * @param interpolatedTime The value of the normalized time (0.0 to 1.0) + * after it has been run through the interpolation function. + * @param t The Transofrmation object to fill in with the current + * transforms. + */ + protected void applyTransformation(float interpolatedTime, Transformation t) { + } + + /** + * Convert the information in the description of a size to an actual + * dimension + * + * @param type One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param value The dimension associated with the type parameter + * @param size The size of the object being animated + * @param parentSize The size of the parent of the object being animated + * @return The dimension to use for the animation + */ + protected float resolveSize(int type, float value, int size, int parentSize) { + switch (type) { + case ABSOLUTE: + return value; + case RELATIVE_TO_SELF: + return size * value; + case RELATIVE_TO_PARENT: + return parentSize * value; + default: + return value; + } + } + + /** + * Utility class to parse a string description of a size. + */ + protected static class Description { + /** + * One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + */ + public int type; + + /** + * The absolute or relative dimension for this Description. + */ + public float value; + + /** + * Size descriptions can appear inthree forms: + * <ol> + * <li>An absolute size. This is represented by a number.</li> + * <li>A size relative to the size of the object being animated. This + * is represented by a number followed by "%".</li> * + * <li>A size relative to the size of the parent of object being + * animated. This is represented by a number followed by "%p".</li> + * </ol> + * @param value The typed value to parse + * @return The parsed version of the description + */ + static Description parseValue(TypedValue value) { + Description d = new Description(); + if (value == null) { + d.type = ABSOLUTE; + d.value = 0; + } else { + if (value.type == TypedValue.TYPE_FRACTION) { + d.type = (value.data & TypedValue.COMPLEX_UNIT_MASK) == + TypedValue.COMPLEX_UNIT_FRACTION_PARENT ? + RELATIVE_TO_PARENT : RELATIVE_TO_SELF; + d.value = TypedValue.complexToFloat(value.data); + return d; + } else if (value.type == TypedValue.TYPE_FLOAT) { + d.type = ABSOLUTE; + d.value = value.getFloat(); + return d; + } else if (value.type >= TypedValue.TYPE_FIRST_INT && + value.type <= TypedValue.TYPE_LAST_INT) { + d.type = ABSOLUTE; + d.value = value.data; + return d; + } + } + + d.type = ABSOLUTE; + d.value = 0.0f; + + return d; + } + } + + /** + * <p>An animation listener receives notifications from an animation. + * Notifications indicate animation related events, such as the end or the + * repetition of the animation.</p> + */ + public static interface AnimationListener { + /** + * <p>Notifies the start of the animation.</p> + * + * @param animation The started animation. + */ + void onAnimationStart(Animation animation); + + /** + * <p>Notifies the end of the animation. This callback is invoked + * only for animation with repeat mode set to NO_REPEAT.</p> + * + * @param animation The animation which reached its end. + */ + void onAnimationEnd(Animation animation); + + /** + * <p>Notifies the repetition of the animation. This callback is invoked + * only for animation with repeat mode set to RESTART or REVERSE.</p> + * + * @param animation The animation which was repeated. + */ + void onAnimationRepeat(Animation animation); + } +} diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java new file mode 100644 index 0000000..3c5920f --- /dev/null +++ b/core/java/android/view/animation/AnimationSet.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a group of Animations that should be played together. + * The transformation of each individual animation are composed + * together into a single transform. + * If AnimationSet sets any properties that its children also set + * (for example, duration or fillBefore), the values of AnimationSet + * override the child values. + */ +public class AnimationSet extends Animation { + private static final int PROPERTY_FILL_AFTER_MASK = 0x1; + private static final int PROPERTY_FILL_BEFORE_MASK = 0x2; + private static final int PROPERTY_REPEAT_MODE_MASK = 0x4; + private static final int PROPERTY_START_OFFSET_MASK = 0x8; + private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10; + private static final int PROPERTY_DURATION_MASK = 0x20; + private static final int PROPERTY_MORPH_MATRIX_MASK = 0x40; + + private int mFlags = 0; + + private ArrayList<Animation> mAnimations = new ArrayList<Animation>(); + + private Transformation mTempTransformation = new Transformation(); + + private long mLastEnd; + + private long[] mStoredOffsets; + + /** + * Constructor used whan an AnimationSet is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public AnimationSet(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AnimationSet); + + setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, + a.getBoolean(com.android.internal.R.styleable.AnimationSet_shareInterpolator, true)); + init(); + + a.recycle(); + } + + + /** + * Constructor to use when building an AnimationSet from code + * + * @param shareInterpolator Pass true if all of the animations in this set + * should use the interpolator assocciated with this AnimationSet. + * Pass false if each animation should use its own interpolator. + */ + public AnimationSet(boolean shareInterpolator) { + setFlag(PROPERTY_SHARE_INTERPOLATOR_MASK, shareInterpolator); + init(); + } + + private void setFlag(int mask, boolean value) { + if (value) { + mFlags |= mask; + } else { + mFlags &= ~mask; + } + } + + private void init() { + mStartTime = 0; + mDuration = 0; + } + + @Override + public void setFillAfter(boolean fillAfter) { + mFlags |= PROPERTY_FILL_AFTER_MASK; + super.setFillAfter(fillAfter); + } + + @Override + public void setFillBefore(boolean fillBefore) { + mFlags |= PROPERTY_FILL_BEFORE_MASK; + super.setFillBefore(fillBefore); + } + + @Override + public void setRepeatMode(int repeatMode) { + mFlags |= PROPERTY_REPEAT_MODE_MASK; + super.setRepeatMode(repeatMode); + } + + @Override + public void setStartOffset(long startOffset) { + mFlags |= PROPERTY_START_OFFSET_MASK; + super.setStartOffset(startOffset); + } + + /** + * <p>Sets the duration of every child animation.</p> + * + * @param durationMillis the duration of the animation, in milliseconds, for + * every child in this set + */ + @Override + public void setDuration(long durationMillis) { + mFlags |= PROPERTY_DURATION_MASK; + super.setDuration(durationMillis); + } + + /** + * Add a child animation to this animation set. + * The transforms of the child animations are applied in the order + * that they were added + * @param a Animation to add. + */ + public void addAnimation(Animation a) { + mAnimations.add(a); + + boolean noMatrix = (mFlags & PROPERTY_MORPH_MATRIX_MASK) == 0; + if (noMatrix && a.willChangeTransformationMatrix()) { + mFlags |= PROPERTY_MORPH_MATRIX_MASK; + } + + if (mAnimations.size() == 1) { + mDuration = a.getStartOffset() + a.getDuration(); + mLastEnd = mStartOffset + mDuration; + } else { + mLastEnd = Math.max(mLastEnd, a.getStartOffset() + a.getDuration()); + mDuration = mLastEnd - mStartOffset; + } + } + + /** + * Sets the start time of this animation and all child animations + * + * @see android.view.animation.Animation#setStartTime(long) + */ + @Override + public void setStartTime(long startTimeMillis) { + super.setStartTime(startTimeMillis); + + final int count = mAnimations.size(); + final ArrayList<Animation> animations = mAnimations; + + for (int i = 0; i < count; i++) { + Animation a = animations.get(i); + a.setStartTime(startTimeMillis); + } + } + + @Override + public long getStartTime() { + long startTime = Long.MAX_VALUE; + + final int count = mAnimations.size(); + final ArrayList<Animation> animations = mAnimations; + + for (int i = 0; i < count; i++) { + Animation a = animations.get(i); + startTime = Math.min(startTime, a.getStartTime()); + } + + return startTime; + } + + @Override + public void restrictDuration(long durationMillis) { + super.restrictDuration(durationMillis); + + final ArrayList<Animation> animations = mAnimations; + int count = animations.size(); + + for (int i = 0; i < count; i++) { + animations.get(i).restrictDuration(durationMillis); + } + } + + /** + * The duration of an AnimationSet is defined to be the + * duration of the longest child animation. + * + * @see android.view.animation.Animation#getDuration() + */ + @Override + public long getDuration() { + final ArrayList<Animation> animations = mAnimations; + final int count = animations.size(); + long duration = 0; + + boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK; + if (durationSet) { + duration = mDuration; + } else { + for (int i = 0; i < count; i++) { + duration = Math.max(duration, animations.get(i).getDuration()); + } + } + + return duration; + } + + /** + * The transformation of an animation set is the concatenation of all of its + * component animations. + * + * @see android.view.animation.Animation#getTransformation + */ + @Override + public boolean getTransformation(long currentTime, Transformation t) { + final int count = mAnimations.size(); + final ArrayList<Animation> animations = mAnimations; + final Transformation temp = mTempTransformation; + + boolean more = false; + boolean started = false; + boolean ended = true; + + t.clear(); + + for (int i = count - 1; i >= 0; --i) { + final Animation a = animations.get(i); + + temp.clear(); + more = a.getTransformation(currentTime, temp) || more; + t.compose(temp); + + started = started || a.hasStarted(); + ended = a.hasEnded() && ended; + } + + if (started && !mStarted) { + if (mListener != null) { + mListener.onAnimationStart(this); + } + mStarted = true; + } + + if (ended != mEnded) { + if (mListener != null) { + mListener.onAnimationEnd(this); + } + mEnded = ended; + } + + return more; + } + + /** + * @see android.view.animation.Animation#scaleCurrentDuration(float) + */ + @Override + public void scaleCurrentDuration(float scale) { + final ArrayList<Animation> animations = mAnimations; + int count = animations.size(); + for (int i = 0; i < count; i++) { + animations.get(i).scaleCurrentDuration(scale); + } + } + + /** + * @see android.view.animation.Animation#initialize(int, int, int, int) + */ + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + + boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK; + boolean fillAfterSet = (mFlags & PROPERTY_FILL_AFTER_MASK) == PROPERTY_FILL_AFTER_MASK; + boolean fillBeforeSet = (mFlags & PROPERTY_FILL_BEFORE_MASK) == PROPERTY_FILL_BEFORE_MASK; + boolean repeatModeSet = (mFlags & PROPERTY_REPEAT_MODE_MASK) == PROPERTY_REPEAT_MODE_MASK; + boolean shareInterpolator = (mFlags & PROPERTY_SHARE_INTERPOLATOR_MASK) + == PROPERTY_SHARE_INTERPOLATOR_MASK; + boolean startOffsetSet = (mFlags & PROPERTY_START_OFFSET_MASK) + == PROPERTY_START_OFFSET_MASK; + + if (shareInterpolator) { + ensureInterpolator(); + } + + final ArrayList<Animation> children = mAnimations; + final int count = children.size(); + + final long duration = mDuration; + final boolean fillAfter = mFillAfter; + final boolean fillBefore = mFillBefore; + final int repeatMode = mRepeatMode; + final Interpolator interpolator = mInterpolator; + final long startOffset = mStartOffset; + + for (int i = 0; i < count; i++) { + Animation a = children.get(i); + if (durationSet) { + a.setDuration(duration); + } + if (fillAfterSet) { + a.setFillAfter(fillAfter); + } + if (fillBeforeSet) { + a.setFillBefore(fillBefore); + } + if (repeatModeSet) { + a.setRepeatMode(repeatMode); + } + if (shareInterpolator) { + a.setInterpolator(interpolator); + } + if (startOffsetSet) { + a.setStartOffset(startOffset); + } + a.initialize(width, height, parentWidth, parentHeight); + } + } + + /** + * @hide + * @param startOffset the startOffset to add to the children's startOffset + */ + void saveChildrenStartOffset(long startOffset) { + final ArrayList<Animation> children = mAnimations; + final int count = children.size(); + long[] storedOffsets = mStoredOffsets = new long[count]; + + for (int i = 0; i < count; i++) { + Animation animation = children.get(i); + long offset = animation.getStartOffset(); + animation.setStartOffset(offset + startOffset); + storedOffsets[i] = offset; + } + } + + /** + * @hide + */ + void restoreChildrenStartOffset() { + final ArrayList<Animation> children = mAnimations; + final int count = children.size(); + final long[] offsets = mStoredOffsets; + + for (int i = 0; i < count; i++) { + children.get(i).setStartOffset(offsets[i]); + } + } + + /** + * @return All the child animations in this AnimationSet. Note that + * this may include other AnimationSets, which are not expanded. + */ + public List<Animation> getAnimations() { + return mAnimations; + } + + @Override + public boolean willChangeTransformationMatrix() { + return (mFlags & PROPERTY_MORPH_MATRIX_MASK) == PROPERTY_MORPH_MATRIX_MASK; + } +} diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java new file mode 100644 index 0000000..ce3cdc5 --- /dev/null +++ b/core/java/android/view/animation/AnimationUtils.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2007 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.view.animation; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.res.XmlResourceParser; +import android.content.res.Resources.NotFoundException; +import android.util.AttributeSet; +import android.util.Xml; +import android.os.SystemClock; + +import java.io.IOException; + +/** + * Defines common utilities for working with animations. + * + */ +public class AnimationUtils { + /** + * Returns the current animation time in milliseconds. This time should be used when invoking + * {@link Animation#setStartTime(long)}. Refer to {@link android.os.SystemClock} for more + * information about the different available clocks. The clock used by this method is + * <em>not</em> the "wall" clock (it is not {@link System#currentTimeMillis}). + * + * @return the current animation time in milliseconds + * + * @see android.os.SystemClock + */ + public static long currentAnimationTimeMillis() { + return SystemClock.uptimeMillis(); + } + + /** + * Loads an {@link Animation} object from a resource + * + * @param context Application context used to access resources + * @param id The resource id of the animation to load + * @return The animation object reference by the specified id + * @throws NotFoundException when the animation cannot be loaded + */ + public static Animation loadAnimation(Context context, int id) + throws NotFoundException { + + XmlResourceParser parser = null; + try { + parser = context.getResources().getAnimation(id); + return createAnimationFromXml(context, parser); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException( + "Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException( + "Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } finally { + if (parser != null) parser.close(); + } + } + + private static Animation createAnimationFromXml(Context c, XmlPullParser parser) + throws XmlPullParserException, IOException { + return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser)); + } + + private static Animation createAnimationFromXml(Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs) + throws XmlPullParserException, IOException { + + Animation anim = null; + + // Make sure we are on a start tag. + int type = parser.getEventType(); + int depth = parser.getDepth(); + + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + if (name.equals("set")) { + anim = new AnimationSet(c, attrs); + createAnimationFromXml(c, parser, (AnimationSet)anim, attrs); + } else if (name.equals("alpha")) { + anim = new AlphaAnimation(c, attrs); + } else if (name.equals("scale")) { + anim = new ScaleAnimation(c, attrs); + } else if (name.equals("rotate")) { + anim = new RotateAnimation(c, attrs); + } else if (name.equals("translate")) { + anim = new TranslateAnimation(c, attrs); + } else { + throw new RuntimeException("Unknown animation name: " + parser.getName()); + } + + if (parent != null) { + parent.addAnimation(anim); + } + } + + return anim; + + } + + public static LayoutAnimationController loadLayoutAnimation( + Context context, int id) throws NotFoundException { + + XmlResourceParser parser = null; + try { + parser = context.getResources().getAnimation(id); + return createLayoutAnimationFromXml(context, parser); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException( + "Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException( + "Can't load animation resource ID #0x" + + Integer .toHexString(id)); + rnf.initCause(ex); + throw rnf; + } finally { + if (parser != null) parser.close(); + } + } + + private static LayoutAnimationController createLayoutAnimationFromXml( + Context c, XmlPullParser parser) + throws XmlPullParserException, IOException { + return createLayoutAnimationFromXml(c, parser, + Xml.asAttributeSet(parser)); + } + + private static LayoutAnimationController createLayoutAnimationFromXml( + Context c, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException { + + LayoutAnimationController controller = null; + + int type; + int depth = parser.getDepth(); + + while (((type = parser.next()) != XmlPullParser.END_TAG + || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + String name = parser.getName(); + + if ("layoutAnimation".equals(name)) { + controller = new LayoutAnimationController(c, attrs); + } else if ("gridLayoutAnimation".equals(name)) { + controller = new GridLayoutAnimationController(c, attrs); + } else { + throw new RuntimeException("Unknown layout animation name: " + + name); + } + } + + return controller; + } + + /** + * Make an animation for objects becoming visible. Uses a slide and fade + * effect. + * + * @param c Context for loading resources + * @param fromLeft is the object to be animated coming from the left + * @return The new animation + */ + public static Animation makeInAnimation(Context c, boolean fromLeft) + { + + Animation a; + if (fromLeft) { + a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_left); + } else { + a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_right); + } + + a.setInterpolator(new DecelerateInterpolator()); + a.setStartTime(currentAnimationTimeMillis()); + return a; + } + + /** + * Make an animation for objects becoming invisible. Uses a slide and fade + * effect. + * + * @param c Context for loading resources + * @param toRight is the object to be animated exiting to the right + * @return The new animation + */ + public static Animation makeOutAnimation(Context c, boolean toRight) + { + + Animation a; + if (toRight) { + a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_right); + } else { + a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_out_left); + } + + a.setInterpolator(new AccelerateInterpolator()); + a.setStartTime(currentAnimationTimeMillis()); + return a; + } + + + /** + * Make an animation for objects becoming visible. Uses a slide up and fade + * effect. + * + * @param c Context for loading resources + * @return The new animation + */ + public static Animation makeInChildBottomAnimation(Context c) + { + + Animation a; + a = AnimationUtils.loadAnimation(c, com.android.internal.R.anim.slide_in_child_bottom); + a.setInterpolator(new AccelerateInterpolator()); + a.setStartTime(currentAnimationTimeMillis()); + return a; + } + + /** + * Loads an {@link Interpolator} object from a resource + * + * @param context Application context used to access resources + * @param id The resource id of the animation to load + * @return The animation object reference by the specified id + * @throws NotFoundException + */ + public static Interpolator loadInterpolator(Context context, int id) + throws NotFoundException { + + XmlResourceParser parser = null; + try { + parser = context.getResources().getAnimation(id); + return createInterpolatorFromXml(context, parser); + } catch (XmlPullParserException ex) { + NotFoundException rnf = new NotFoundException( + "Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } catch (IOException ex) { + NotFoundException rnf = new NotFoundException( + "Can't load animation resource ID #0x" + + Integer.toHexString(id)); + rnf.initCause(ex); + throw rnf; + } finally { + if (parser != null) parser.close(); + } + + } + + private static Interpolator createInterpolatorFromXml(Context c, XmlPullParser parser) + throws XmlPullParserException, IOException { + + Interpolator interpolator = null; + + // Make sure we are on a start tag. + int type = parser.getEventType(); + int depth = parser.getDepth(); + + while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { + + if (type != XmlPullParser.START_TAG) { + continue; + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + String name = parser.getName(); + + + if (name.equals("linearInterpolator")) { + interpolator = new LinearInterpolator(c, attrs); + } else if (name.equals("accelerateInterpolator")) { + interpolator = new AccelerateInterpolator(c, attrs); + } else if (name.equals("decelerateInterpolator")) { + interpolator = new DecelerateInterpolator(c, attrs); + } else if (name.equals("accelerateDecelerateInterpolator")) { + interpolator = new AccelerateDecelerateInterpolator(c, attrs); + } else if (name.equals("cycleInterpolator")) { + interpolator = new CycleInterpolator(c, attrs); + } else { + throw new RuntimeException("Unknown interpolator name: " + parser.getName()); + } + + } + + return interpolator; + + } +} diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java new file mode 100644 index 0000000..d355c23 --- /dev/null +++ b/core/java/android/view/animation/CycleInterpolator.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +/** + * Repeats the animation for a specified number of cycles. The + * rate of change follows a sinusoidal pattern. + * + */ +public class CycleInterpolator implements Interpolator { + public CycleInterpolator(float cycles) { + mCycles = cycles; + } + + public CycleInterpolator(Context context, AttributeSet attrs) { + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CycleInterpolator); + + mCycles = a.getFloat(com.android.internal.R.styleable.CycleInterpolator_cycles, 1.0f); + + a.recycle(); + } + + public float getInterpolation(float input) { + return (float)(Math.sin(2 * mCycles * Math.PI * input)); + } + + private float mCycles; +} diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java new file mode 100644 index 0000000..176169e --- /dev/null +++ b/core/java/android/view/animation/DecelerateInterpolator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +/** + * An interpolator where the rate of change starts out quickly and + * and then decelerates. + * + */ +public class DecelerateInterpolator implements Interpolator { + public DecelerateInterpolator() { + } + + /** + * Constructor + * + * @param factor Degree to which the animation should be eased. Seting factor to 1.0f produces + * an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the + * ease-out effect (i.e., it starts even faster and ends evens slower) + */ + public DecelerateInterpolator(float factor) { + mFactor = factor; + } + + public DecelerateInterpolator(Context context, AttributeSet attrs) { + TypedArray a = + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.DecelerateInterpolator); + + mFactor = a.getFloat(com.android.internal.R.styleable.DecelerateInterpolator_factor, 1.0f); + + a.recycle(); + } + + public float getInterpolation(float input) { + if (mFactor == 1.0f) { + return (float)(1.0f - (1.0f - input) * (1.0f - input)); + } else { + return (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor)); + } + } + + private float mFactor = 1.0f; +} diff --git a/core/java/android/view/animation/GridLayoutAnimationController.java b/core/java/android/view/animation/GridLayoutAnimationController.java new file mode 100644 index 0000000..9161d8b --- /dev/null +++ b/core/java/android/view/animation/GridLayoutAnimationController.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2007 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.view.animation; + +import android.view.View; +import android.view.ViewGroup; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import java.util.Random; + +/** + * A layout animation controller is used to animated a grid layout's children. + * + * While {@link LayoutAnimationController} relies only on the index of the child + * in the view group to compute the animation delay, this class uses both the + * X and Y coordinates of the child within a grid. + * + * In addition, the animation direction can be controlled. The default direction + * is <code>DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM</code>. You can + * also set the animation priority to columns or rows. The default priority is + * none. + * + * Information used to compute the animation delay of each child are stored + * in an instance of + * {@link android.view.animation.GridLayoutAnimationController.AnimationParameters}, + * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view. + * + * @see LayoutAnimationController + * @see android.widget.GridView + * + * @attr ref android.R.styleable#GridLayoutAnimation_columnDelay + * @attr ref android.R.styleable#GridLayoutAnimation_rowDelay + * @attr ref android.R.styleable#GridLayoutAnimation_direction + * @attr ref android.R.styleable#GridLayoutAnimation_directionPriority + */ +public class GridLayoutAnimationController extends LayoutAnimationController { + /** + * Animates the children starting from the left of the grid to the right. + */ + public static final int DIRECTION_LEFT_TO_RIGHT = 0x0; + + /** + * Animates the children starting from the right of the grid to the left. + */ + public static final int DIRECTION_RIGHT_TO_LEFT = 0x1; + + /** + * Animates the children starting from the top of the grid to the bottom. + */ + public static final int DIRECTION_TOP_TO_BOTTOM = 0x0; + + /** + * Animates the children starting from the bottom of the grid to the top. + */ + public static final int DIRECTION_BOTTOM_TO_TOP = 0x2; + + /** + * Bitmask used to retrieve the horizontal component of the direction. + */ + public static final int DIRECTION_HORIZONTAL_MASK = 0x1; + + /** + * Bitmask used to retrieve the vertical component of the direction. + */ + public static final int DIRECTION_VERTICAL_MASK = 0x2; + + /** + * Rows and columns are animated at the same time. + */ + public static final int PRIORITY_NONE = 0; + + /** + * Columns are animated first. + */ + public static final int PRIORITY_COLUMN = 1; + + /** + * Rows are animated first. + */ + public static final int PRIORITY_ROW = 2; + + private float mColumnDelay; + private float mRowDelay; + + private int mDirection; + private int mDirectionPriority; + + /** + * Creates a new grid layout animation controller from external resources. + * + * @param context the Context the view group is running in, through which + * it can access the resources + * @param attrs the attributes of the XML tag that is inflating the + * layout animation controller + */ + public GridLayoutAnimationController(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.GridLayoutAnimation); + + Animation.Description d = Animation.Description.parseValue( + a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_columnDelay)); + mColumnDelay = d.value; + d = Animation.Description.parseValue( + a.peekValue(com.android.internal.R.styleable.GridLayoutAnimation_rowDelay)); + mRowDelay = d.value; + //noinspection PointlessBitwiseExpression + mDirection = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_direction, + DIRECTION_LEFT_TO_RIGHT | DIRECTION_TOP_TO_BOTTOM); + mDirectionPriority = a.getInt(com.android.internal.R.styleable.GridLayoutAnimation_directionPriority, + PRIORITY_NONE); + + a.recycle(); + } + + /** + * Creates a new layout animation controller with a delay of 50% + * for both rows and columns and the specified animation. + * + * @param animation the animation to use on each child of the view group + */ + public GridLayoutAnimationController(Animation animation) { + this(animation, 0.5f, 0.5f); + } + + /** + * Creates a new layout animation controller with the specified delays + * and the specified animation. + * + * @param animation the animation to use on each child of the view group + * @param columnDelay the delay by which each column animation must be offset + * @param rowDelay the delay by which each row animation must be offset + */ + public GridLayoutAnimationController(Animation animation, float columnDelay, float rowDelay) { + super(animation); + mColumnDelay = columnDelay; + mRowDelay = rowDelay; + } + + /** + * Returns the delay by which the children's animation are offset from one + * column to the other. The delay is expressed as a fraction of the + * animation duration. + * + * @return a fraction of the animation duration + * + * @see #setColumnDelay(float) + * @see #getRowDelay() + * @see #setRowDelay(float) + */ + public float getColumnDelay() { + return mColumnDelay; + } + + /** + * Sets the delay, as a fraction of the animation duration, by which the + * children's animations are offset from one column to the other. + * + * @param columnDelay a fraction of the animation duration + * + * @see #getColumnDelay() + * @see #getRowDelay() + * @see #setRowDelay(float) + */ + public void setColumnDelay(float columnDelay) { + mColumnDelay = columnDelay; + } + + /** + * Returns the delay by which the children's animation are offset from one + * row to the other. The delay is expressed as a fraction of the + * animation duration. + * + * @return a fraction of the animation duration + * + * @see #setRowDelay(float) + * @see #getColumnDelay() + * @see #setColumnDelay(float) + */ + public float getRowDelay() { + return mRowDelay; + } + + /** + * Sets the delay, as a fraction of the animation duration, by which the + * children's animations are offset from one row to the other. + * + * @param rowDelay a fraction of the animation duration + * + * @see #getRowDelay() + * @see #getColumnDelay() + * @see #setColumnDelay(float) + */ + public void setRowDelay(float rowDelay) { + mRowDelay = rowDelay; + } + + /** + * Returns the direction of the animation. {@link #DIRECTION_HORIZONTAL_MASK} + * and {@link #DIRECTION_VERTICAL_MASK} can be used to retrieve the + * horizontal and vertical components of the direction. + * + * @return the direction of the animation + * + * @see #setDirection(int) + * @see #DIRECTION_BOTTOM_TO_TOP + * @see #DIRECTION_TOP_TO_BOTTOM + * @see #DIRECTION_LEFT_TO_RIGHT + * @see #DIRECTION_RIGHT_TO_LEFT + * @see #DIRECTION_HORIZONTAL_MASK + * @see #DIRECTION_VERTICAL_MASK + */ + public int getDirection() { + return mDirection; + } + + /** + * Sets the direction of the animation. The direction is expressed as an + * integer containing a horizontal and vertical component. For instance, + * <code>DIRECTION_BOTTOM_TO_TOP | DIRECTION_RIGHT_TO_LEFT</code>. + * + * @param direction the direction of the animation + * + * @see #getDirection() + * @see #DIRECTION_BOTTOM_TO_TOP + * @see #DIRECTION_TOP_TO_BOTTOM + * @see #DIRECTION_LEFT_TO_RIGHT + * @see #DIRECTION_RIGHT_TO_LEFT + * @see #DIRECTION_HORIZONTAL_MASK + * @see #DIRECTION_VERTICAL_MASK + */ + public void setDirection(int direction) { + mDirection = direction; + } + + /** + * Returns the direction priority for the animation. The priority can + * be either {@link #PRIORITY_NONE}, {@link #PRIORITY_COLUMN} or + * {@link #PRIORITY_ROW}. + * + * @return the priority of the animation direction + * + * @see #setDirectionPriority(int) + * @see #PRIORITY_COLUMN + * @see #PRIORITY_NONE + * @see #PRIORITY_ROW + */ + public int getDirectionPriority() { + return mDirectionPriority; + } + + /** + * Specifies the direction priority of the animation. For instance, + * {@link #PRIORITY_COLUMN} will give priority to columns: the animation + * will first play on the column, then on the rows.Z + * + * @param directionPriority the direction priority of the animation + * + * @see #getDirectionPriority() + * @see #PRIORITY_COLUMN + * @see #PRIORITY_NONE + * @see #PRIORITY_ROW + */ + public void setDirectionPriority(int directionPriority) { + mDirectionPriority = directionPriority; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean willOverlap() { + return mColumnDelay < 1.0f || mRowDelay < 1.0f; + } + + /** + * {@inheritDoc} + */ + @Override + protected long getDelayForView(View view) { + ViewGroup.LayoutParams lp = view.getLayoutParams(); + AnimationParameters params = (AnimationParameters) lp.layoutAnimationParameters; + + if (params == null) { + return 0; + } + + final int column = getTransformedColumnIndex(params); + final int row = getTransformedRowIndex(params); + + final int rowsCount = params.rowsCount; + final int columnsCount = params.columnsCount; + + final long duration = mAnimation.getDuration(); + final float columnDelay = mColumnDelay * duration; + final float rowDelay = mRowDelay * duration; + + float totalDelay; + long viewDelay; + + if (mInterpolator == null) { + mInterpolator = new LinearInterpolator(); + } + + switch (mDirectionPriority) { + case PRIORITY_COLUMN: + viewDelay = (long) (row * rowDelay + column * rowsCount * rowDelay); + totalDelay = rowsCount * rowDelay + columnsCount * rowsCount * rowDelay; + break; + case PRIORITY_ROW: + viewDelay = (long) (column * columnDelay + row * columnsCount * columnDelay); + totalDelay = columnsCount * columnDelay + rowsCount * columnsCount * columnDelay; + break; + case PRIORITY_NONE: + default: + viewDelay = (long) (column * columnDelay + row * rowDelay); + totalDelay = columnsCount * columnDelay + rowsCount * rowDelay; + break; + } + + float normalizedDelay = viewDelay / totalDelay; + normalizedDelay = mInterpolator.getInterpolation(normalizedDelay); + + return (long) (normalizedDelay * totalDelay); + } + + private int getTransformedColumnIndex(AnimationParameters params) { + int index; + switch (getOrder()) { + case ORDER_REVERSE: + index = params.columnsCount - 1 - params.column; + break; + case ORDER_RANDOM: + if (mRandomizer == null) { + mRandomizer = new Random(); + } + index = (int) (params.columnsCount * mRandomizer.nextFloat()); + break; + case ORDER_NORMAL: + default: + index = params.column; + break; + } + + int direction = mDirection & DIRECTION_HORIZONTAL_MASK; + if (direction == DIRECTION_RIGHT_TO_LEFT) { + index = params.columnsCount - 1 - index; + } + + return index; + } + + private int getTransformedRowIndex(AnimationParameters params) { + int index; + switch (getOrder()) { + case ORDER_REVERSE: + index = params.rowsCount - 1 - params.row; + break; + case ORDER_RANDOM: + if (mRandomizer == null) { + mRandomizer = new Random(); + } + index = (int) (params.rowsCount * mRandomizer.nextFloat()); + break; + case ORDER_NORMAL: + default: + index = params.row; + break; + } + + int direction = mDirection & DIRECTION_VERTICAL_MASK; + if (direction == DIRECTION_BOTTOM_TO_TOP) { + index = params.rowsCount - 1 - index; + } + + return index; + } + + /** + * The set of parameters that has to be attached to each view contained in + * the view group animated by the grid layout animation controller. These + * parameters are used to compute the start time of each individual view's + * animation. + */ + public static class AnimationParameters extends + LayoutAnimationController.AnimationParameters { + /** + * The view group's column to which the view belongs. + */ + public int column; + + /** + * The view group's row to which the view belongs. + */ + public int row; + + /** + * The number of columns in the view's enclosing grid layout. + */ + public int columnsCount; + + /** + * The number of rows in the view's enclosing grid layout. + */ + public int rowsCount; + } +} diff --git a/core/java/android/view/animation/Interpolator.java b/core/java/android/view/animation/Interpolator.java new file mode 100644 index 0000000..d14c3e3 --- /dev/null +++ b/core/java/android/view/animation/Interpolator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2006 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.view.animation; + +/** + * An interpolator defines the rate of change of an animation. This allows + * the basic animation effects (alpha, scale, translate, rotate) to be + * accelerated, decelerated, repeated, etc. + */ +public interface Interpolator { + + /** + * Maps a point on the timeline to a multiplier to be applied to the + * transformations of an animation. + * + * @param input A value between 0 and 1.0 indicating our current point + * in the animation where 0 represents the start and 1.0 represents + * the end + * @return The interpolation value. This value can be more than 1.0 for + * Interpolators which overshoot their targets, or less than 0 for + * Interpolators that undershoot their targets. + */ + float getInterpolation(float input); + +} diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java new file mode 100644 index 0000000..9cfa8d7 --- /dev/null +++ b/core/java/android/view/animation/LayoutAnimationController.java @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2007 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import java.util.Random; + +/** + * A layout animation controller is used to animated a layout's, or a view + * group's, children. Each child uses the same animation but for every one of + * them, the animation starts at a different time. A layout animation controller + * is used by {@link android.view.ViewGroup} to compute the delay by which each + * child's animation start must be offset. The delay is computed by using + * characteristics of each child, like its index in the view group. + * + * This standard implementation computes the delay by multiplying a fixed + * amount of miliseconds by the index of the child in its parent view group. + * Subclasses are supposed to override + * {@link #getDelayForView(android.view.View)} to implement a different way + * of computing the delay. For instance, a + * {@link android.view.animation.GridLayoutAnimationController} will compute the + * delay based on the column and row indices of the child in its parent view + * group. + * + * Information used to compute the animation delay of each child are stored + * in an instance of + * {@link android.view.animation.LayoutAnimationController.AnimationParameters}, + * itself stored in the {@link android.view.ViewGroup.LayoutParams} of the view. + * + * @attr ref android.R.styleable#LayoutAnimation_delay + * @attr ref android.R.styleable#LayoutAnimation_animationOrder + * @attr ref android.R.styleable#LayoutAnimation_interpolator + * @attr ref android.R.styleable#LayoutAnimation_animation + */ +public class LayoutAnimationController { + /** + * Distributes the animation delays in the order in which view were added + * to their view group. + */ + public static final int ORDER_NORMAL = 0; + + /** + * Distributes the animation delays in the reverse order in which view were + * added to their view group. + */ + public static final int ORDER_REVERSE = 1; + + /** + * Randomly distributes the animation delays. + */ + public static final int ORDER_RANDOM = 2; + + /** + * The animation applied on each child of the view group on which this + * layout animation controller is set. + */ + protected Animation mAnimation; + + /** + * The randomizer used when the order is set to random. Subclasses should + * use this object to avoid creating their own. + */ + protected Random mRandomizer; + + /** + * The interpolator used to interpolate the delays. + */ + protected Interpolator mInterpolator; + + private float mDelay; + private int mOrder; + + private long mDuration; + private long mMaxDelay; + + /** + * Creates a new layout animation controller from external resources. + * + * @param context the Context the view group is running in, through which + * it can access the resources + * @param attrs the attributes of the XML tag that is inflating the + * layout animation controller + */ + public LayoutAnimationController(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LayoutAnimation); + + Animation.Description d = Animation.Description.parseValue( + a.peekValue(com.android.internal.R.styleable.LayoutAnimation_delay)); + mDelay = d.value; + + mOrder = a.getInt(com.android.internal.R.styleable.LayoutAnimation_animationOrder, ORDER_NORMAL); + + int resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_animation, 0); + if (resource > 0) { + setAnimation(context, resource); + } + + resource = a.getResourceId(com.android.internal.R.styleable.LayoutAnimation_interpolator, 0); + if (resource > 0) { + setInterpolator(context, resource); + } + + a.recycle(); + } + + /** + * Creates a new layout animation controller with a delay of 50% + * and the specified animation. + * + * @param animation the animation to use on each child of the view group + */ + public LayoutAnimationController(Animation animation) { + this(animation, 0.5f); + } + + /** + * Creates a new layout animation controller with the specified delay + * and the specified animation. + * + * @param animation the animation to use on each child of the view group + * @param delay the delay by which each child's animation must be offset + */ + public LayoutAnimationController(Animation animation, float delay) { + mDelay = delay; + setAnimation(animation); + } + + /** + * Returns the order used to compute the delay of each child's animation. + * + * @return one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or + * {@link #ORDER_RANDOM) + * + * @attr ref android.R.styleable#LayoutAnimation_animationOrder + */ + public int getOrder() { + return mOrder; + } + + /** + * Sets the order used to compute the delay of each child's animation. + * + * @param order one of {@link #ORDER_NORMAL}, {@link #ORDER_REVERSE} or + * {@link #ORDER_RANDOM} + * + * @attr ref android.R.styleable#LayoutAnimation_animationOrder + */ + public void setOrder(int order) { + mOrder = order; + } + + /** + * Sets the animation to be run on each child of the view group on which + * this layout animation controller is . + * + * @param context the context from which the animation must be inflated + * @param resourceID the resource identifier of the animation + * + * @see #setAnimation(Animation) + * @see #getAnimation() + * + * @attr ref android.R.styleable#LayoutAnimation_animation + */ + public void setAnimation(Context context, int resourceID) { + setAnimation(AnimationUtils.loadAnimation(context, resourceID)); + } + + /** + * Sets the animation to be run on each child of the view group on which + * this layout animation controller is . + * + * @param animation the animation to run on each child of the view group + + * @see #setAnimation(android.content.Context, int) + * @see #getAnimation() + * + * @attr ref android.R.styleable#LayoutAnimation_animation + */ + public void setAnimation(Animation animation) { + mAnimation = animation; + mAnimation.setFillBefore(true); + } + + /** + * Returns the animation applied to each child of the view group on which + * this controller is set. + * + * @return an {@link android.view.animation.Animation} instance + * + * @see #setAnimation(android.content.Context, int) + * @see #setAnimation(Animation) + */ + public Animation getAnimation() { + return mAnimation; + } + + /** + * Sets the interpolator used to interpolate the delays between the + * children. + * + * @param context the context from which the interpolator must be inflated + * @param resourceID the resource identifier of the interpolator + * + * @see #getInterpolator() + * @see #setInterpolator(Interpolator) + * + * @attr ref android.R.styleable#LayoutAnimation_interpolator + */ + public void setInterpolator(Context context, int resourceID) { + setInterpolator(AnimationUtils.loadInterpolator(context, resourceID)); + } + + /** + * Sets the interpolator used to interpolate the delays between the + * children. + * + * @param interpolator the interpolator + * + * @see #getInterpolator() + * @see #setInterpolator(Interpolator) + * + * @attr ref android.R.styleable#LayoutAnimation_interpolator + */ + public void setInterpolator(Interpolator interpolator) { + mInterpolator = interpolator; + } + + /** + * Returns the interpolator used to interpolate the delays between the + * children. + * + * @return an {@link android.view.animation.Interpolator} + */ + public Interpolator getInterpolator() { + return mInterpolator; + } + + /** + * Returns the delay by which the children's animation are offset. The + * delay is expressed as a fraction of the animation duration. + * + * @return a fraction of the animation duration + * + * @see #setDelay(float) + */ + public float getDelay() { + return mDelay; + } + + /** + * Sets the delay, as a fraction of the animation duration, by which the + * children's animations are offset. The general formula is: + * + * <pre> + * child animation delay = child index * delay * animation duration + * </pre> + * + * @param delay a fraction of the animation duration + * + * @see #getDelay() + */ + public void setDelay(float delay) { + mDelay = delay; + } + + /** + * Indicates whether two children's animations will overlap. Animations + * overlap when the delay is lower than 100% (or 1.0). + * + * @return true if animations will overlap, false otherwise + */ + public boolean willOverlap() { + return mDelay < 1.0f; + } + + /** + * Starts the animation. + */ + public void start() { + mDuration = mAnimation.getDuration(); + mMaxDelay = Long.MIN_VALUE; + mAnimation.setStartTime(-1); + } + + /** + * Returns the animation to be applied to the specified view. The returned + * animation is delayed by an offset computed according to the information + * provided by + * {@link android.view.animation.LayoutAnimationController.AnimationParameters}. + * This method is called by view groups to obtain the animation to set on + * a specific child. + * + * @param view the view to animate + * @return an animation delayed by the number of milliseconds returned by + * {@link #getDelayForView(android.view.View)} + * + * @see #getDelay() + * @see #setDelay(float) + * @see #getDelayForView(android.view.View) + */ + public final Animation getAnimationForView(View view) { + final long delay = getDelayForView(view); + mMaxDelay = Math.max(mMaxDelay, delay); + return new DelayedAnimation(delay, mAnimation); + } + + /** + * Indicates whether the layout animation is over or not. A layout animation + * is considered done when the animation with the longest delay is done. + * + * @return true if all of the children's animations are over, false otherwise + */ + public boolean isDone() { + return AnimationUtils.currentAnimationTimeMillis() > + mAnimation.getStartTime() + mMaxDelay + mDuration; + } + + /** + * Returns the amount of milliseconds by which the specified view's + * animation must be delayed or offset. Subclasses should override this + * method to return a suitable value. + * + * This implementation returns <code>child animation delay</code> + * milliseconds where: + * + * <pre> + * child animation delay = child index * delay + * </pre> + * + * The index is retrieved from the + * {@link android.view.animation.LayoutAnimationController.AnimationParameters} + * found in the view's {@link android.view.ViewGroup.LayoutParams}. + * + * @param view the view for which to obtain the animation's delay + * @return a delay in milliseconds + * + * @see #getAnimationForView(android.view.View) + * @see #getDelay() + * @see #getTransformedIndex(android.view.animation.LayoutAnimationController.AnimationParameters) + * @see android.view.ViewGroup.LayoutParams + */ + protected long getDelayForView(View view) { + ViewGroup.LayoutParams lp = view.getLayoutParams(); + AnimationParameters params = lp.layoutAnimationParameters; + + if (params == null) { + return 0; + } + + final float delay = mDelay * mAnimation.getDuration(); + final long viewDelay = (long) (getTransformedIndex(params) * delay); + final float totalDelay = delay * params.count; + + if (mInterpolator == null) { + mInterpolator = new LinearInterpolator(); + } + + float normalizedDelay = viewDelay / totalDelay; + normalizedDelay = mInterpolator.getInterpolation(normalizedDelay); + + return (long) (normalizedDelay * totalDelay); + } + + /** + * Transforms the index stored in + * {@link android.view.animation.LayoutAnimationController.AnimationParameters} + * by the order returned by {@link #getOrder()}. Subclasses should override + * this method to provide additional support for other types of ordering. + * This method should be invoked by + * {@link #getDelayForView(android.view.View)} prior to any computation. + * + * @param params the animation parameters containing the index + * @return a transformed index + */ + protected int getTransformedIndex(AnimationParameters params) { + switch (getOrder()) { + case ORDER_REVERSE: + return params.count - 1 - params.index; + case ORDER_RANDOM: + if (mRandomizer == null) { + mRandomizer = new Random(); + } + return (int) (params.count * mRandomizer.nextFloat()); + case ORDER_NORMAL: + default: + return params.index; + } + } + + /** + * The set of parameters that has to be attached to each view contained in + * the view group animated by the layout animation controller. These + * parameters are used to compute the start time of each individual view's + * animation. + */ + public static class AnimationParameters { + /** + * The number of children in the view group containing the view to which + * these parameters are attached. + */ + public int count; + + /** + * The index of the view to which these parameters are attached in its + * containing view group. + */ + public int index; + } + + /** + * Encapsulates an animation and delays its start offset by a specified + * amount. This allows to reuse the same base animation for various views + * and get the effect of running multiple instances of the animation at + * different times. + */ + private static class DelayedAnimation extends Animation { + private final long mDelay; + private final Animation mAnimation; + + /** + * Creates a new delayed animation that will delay the controller's + * animation by the specified delay in milliseconds. + * + * @param delay the delay in milliseconds by which to offset the + * @param animation the animation to delay + */ + private DelayedAnimation(long delay, Animation animation) { + mDelay = delay; + mAnimation = animation; + } + + @Override + public boolean isInitialized() { + return mAnimation.isInitialized(); + } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + mAnimation.initialize(width, height, parentWidth, parentHeight); + } + + @Override + public void reset() { + mAnimation.reset(); + } + + @Override + public boolean getTransformation(long currentTime, Transformation outTransformation) { + final long oldOffset = mAnimation.getStartOffset(); + final boolean isSet = mAnimation instanceof AnimationSet; + if (isSet) { + AnimationSet set = ((AnimationSet) mAnimation); + set.saveChildrenStartOffset(mDelay); + } + mAnimation.setStartOffset(oldOffset + mDelay); + + boolean result = mAnimation.getTransformation(currentTime, + outTransformation); + + if (isSet) { + AnimationSet set = ((AnimationSet) mAnimation); + set.restoreChildrenStartOffset(); + } + mAnimation.setStartOffset(oldOffset); + + return result; + } + + @Override + public void setStartTime(long startTimeMillis) { + mAnimation.setStartTime(startTimeMillis); + } + + @Override + public long getStartTime() { + return mAnimation.getStartTime(); + } + + @Override + public void setInterpolator(Interpolator i) { + mAnimation.setInterpolator(i); + } + + @Override + public void setStartOffset(long startOffset) { + mAnimation.setStartOffset(startOffset); + } + + @Override + public void setDuration(long durationMillis) { + mAnimation.setDuration(durationMillis); + } + + @Override + public void scaleCurrentDuration(float scale) { + mAnimation.scaleCurrentDuration(scale); + } + + @Override + public void setRepeatMode(int repeatMode) { + mAnimation.setRepeatMode(repeatMode); + } + + @Override + public void setFillBefore(boolean fillBefore) { + mAnimation.setFillBefore(fillBefore); + } + + @Override + public void setFillAfter(boolean fillAfter) { + mAnimation.setFillAfter(fillAfter); + } + + @Override + public Interpolator getInterpolator() { + return mAnimation.getInterpolator(); + } + + @Override + public long getDuration() { + return mAnimation.getDuration(); + } + + @Override + public long getStartOffset() { + return mAnimation.getStartOffset() + mDelay; + } + + @Override + public int getRepeatMode() { + return mAnimation.getRepeatMode(); + } + + @Override + public boolean getFillBefore() { + return mAnimation.getFillBefore(); + } + + @Override + public boolean getFillAfter() { + return mAnimation.getFillAfter(); + } + + @Override + public boolean willChangeTransformationMatrix() { + return mAnimation.willChangeTransformationMatrix(); + } + + @Override + public boolean willChangeBounds() { + return mAnimation.willChangeBounds(); + } + } +} diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java new file mode 100644 index 0000000..96a039f --- /dev/null +++ b/core/java/android/view/animation/LinearInterpolator.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.util.AttributeSet; + +/** + * An interpolator where the rate of change is constant + * + */ +public class LinearInterpolator implements Interpolator { + + public LinearInterpolator() { + } + + public LinearInterpolator(Context context, AttributeSet attrs) { + } + + public float getInterpolation(float input) { + return input; + } +} diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java new file mode 100644 index 0000000..2f51b91 --- /dev/null +++ b/core/java/android/view/animation/RotateAnimation.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +/** + * An animation that controls the rotation of an object. This rotation takes + * place int the X-Y plane. You can specify the point to use for the center of + * the rotation, where (0,0) is the top left point. If not specified, (0,0) is + * the default rotation point. + * + */ +public class RotateAnimation extends Animation { + private float mFromDegrees; + private float mToDegrees; + + private int mPivotXType = ABSOLUTE; + private int mPivotYType = ABSOLUTE; + private float mPivotXValue = 0.0f; + private float mPivotYValue = 0.0f; + + private float mPivotX; + private float mPivotY; + + /** + * Constructor used whan an RotateAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public RotateAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.RotateAnimation); + + mFromDegrees = a.getFloat( + com.android.internal.R.styleable.RotateAnimation_fromDegrees, 0.0f); + mToDegrees = a.getFloat(com.android.internal.R.styleable.RotateAnimation_toDegrees, 0.0f); + + Description d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.RotateAnimation_pivotX)); + mPivotXType = d.type; + mPivotXValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.RotateAnimation_pivotY)); + mPivotYType = d.type; + mPivotYValue = d.value; + + a.recycle(); + } + + /** + * Constructor to use when building a RotateAnimation from code. + * Default pivotX/pivotY point is (0,0). + * + * @param fromDegrees Rotation offset to apply at the start of the + * animation. + * + * @param toDegrees Rotation offset to apply at the end of the animation. + */ + public RotateAnimation(float fromDegrees, float toDegrees) { + mFromDegrees = fromDegrees; + mToDegrees = toDegrees; + mPivotX = 0.0f; + mPivotY = 0.0f; + } + + /** + * Constructor to use when building a RotateAnimation from code + * + * @param fromDegrees Rotation offset to apply at the start of the + * animation. + * + * @param toDegrees Rotation offset to apply at the end of the animation. + * + * @param pivotX The X coordinate of the point about which the object is + * being rotated, specified as an absolute number where 0 is the left + * edge. + * @param pivotY The Y coordinate of the point about which the object is + * being rotated, specified as an absolute number where 0 is the top + * edge. + */ + public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY) { + mFromDegrees = fromDegrees; + mToDegrees = toDegrees; + + mPivotXType = ABSOLUTE; + mPivotYType = ABSOLUTE; + mPivotXValue = pivotX; + mPivotYValue = pivotY; + } + + /** + * Constructor to use when building a RotateAnimation from code + * + * @param fromDegrees Rotation offset to apply at the start of the + * animation. + * + * @param toDegrees Rotation offset to apply at the end of the animation. + * + * @param pivotXType Specifies how pivotXValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param pivotXValue The X coordinate of the point about which the object + * is being rotated, specified as an absolute number where 0 is the + * left edge. This value can either be an absolute number if + * pivotXType is ABSOLUTE, or a percentage (where 1.0 is 100%) + * otherwise. + * @param pivotYType Specifies how pivotYValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param pivotYValue The Y coordinate of the point about which the object + * is being rotated, specified as an absolute number where 0 is the + * top edge. This value can either be an absolute number if + * pivotYType is ABSOLUTE, or a percentage (where 1.0 is 100%) + * otherwise. + */ + public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, + int pivotYType, float pivotYValue) { + mFromDegrees = fromDegrees; + mToDegrees = toDegrees; + + mPivotXValue = pivotXValue; + mPivotXType = pivotXType; + mPivotYValue = pivotYValue; + mPivotYType = pivotYType; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime); + + if (mPivotX == 0.0f && mPivotY == 0.0f) { + t.getMatrix().setRotate(degrees); + } else { + t.getMatrix().setRotate(degrees, mPivotX, mPivotY); + } + } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth); + mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight); + } +} diff --git a/core/java/android/view/animation/ScaleAnimation.java b/core/java/android/view/animation/ScaleAnimation.java new file mode 100644 index 0000000..122ed6d --- /dev/null +++ b/core/java/android/view/animation/ScaleAnimation.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +/** + * An animation that controls the scale of an object. You can specify the point + * to use for the center of scaling. + * + */ +public class ScaleAnimation extends Animation { + private float mFromX; + private float mToX; + private float mFromY; + private float mToY; + + private int mPivotXType = ABSOLUTE; + private int mPivotYType = ABSOLUTE; + private float mPivotXValue = 0.0f; + private float mPivotYValue = 0.0f; + + private float mPivotX; + private float mPivotY; + + /** + * Constructor used whan an ScaleAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public ScaleAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ScaleAnimation); + + mFromX = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_fromXScale, 0.0f); + mToX = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_toXScale, 0.0f); + + mFromY = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_fromYScale, 0.0f); + mToY = a.getFloat(com.android.internal.R.styleable.ScaleAnimation_toYScale, 0.0f); + + Description d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ScaleAnimation_pivotX)); + mPivotXType = d.type; + mPivotXValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ScaleAnimation_pivotY)); + mPivotYType = d.type; + mPivotYValue = d.value; + + a.recycle(); + } + + /** + * Constructor to use when building a ScaleAnimation from code + * + * @param fromX Horizontal scaling factor to apply at the start of the + * animation + * @param toX Horizontal scaling factor to apply at the end of the animation + * @param fromY Vertical scaling factor to apply at the start of the + * animation + * @param toY Vertical scaling factor to apply at the end of the animation + */ + public ScaleAnimation(float fromX, float toX, float fromY, float toY) { + mFromX = fromX; + mToX = toX; + mFromY = fromY; + mToY = toY; + mPivotX = 0; + mPivotY = 0; + } + + /** + * Constructor to use when building a ScaleAnimation from code + * + * @param fromX Horizontal scaling factor to apply at the start of the + * animation + * @param toX Horizontal scaling factor to apply at the end of the animation + * @param fromY Vertical scaling factor to apply at the start of the + * animation + * @param toY Vertical scaling factor to apply at the end of the animation + * @param pivotX The X coordinate of the point about which the object is + * being scaled, specified as an absolute number where 0 is the left + * edge. (This point remains fixed while the object changes size.) + * @param pivotY The Y coordinate of the point about which the object is + * being scaled, specified as an absolute number where 0 is the top + * edge. (This point remains fixed while the object changes size.) + */ + public ScaleAnimation(float fromX, float toX, float fromY, float toY, + float pivotX, float pivotY) { + mFromX = fromX; + mToX = toX; + mFromY = fromY; + mToY = toY; + + mPivotXType = ABSOLUTE; + mPivotYType = ABSOLUTE; + mPivotXValue = pivotX; + mPivotYValue = pivotY; + } + + /** + * Constructor to use when building a ScaleAnimation from code + * + * @param fromX Horizontal scaling factor to apply at the start of the + * animation + * @param toX Horizontal scaling factor to apply at the end of the animation + * @param fromY Vertical scaling factor to apply at the start of the + * animation + * @param toY Vertical scaling factor to apply at the end of the animation + * @param pivotXType Specifies how pivotXValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param pivotXValue The X coordinate of the point about which the object + * is being scaled, specified as an absolute number where 0 is the + * left edge. (This point remains fixed while the object changes + * size.) This value can either be an absolute number if pivotXType + * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise. + * @param pivotYType Specifies how pivotYValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param pivotYValue The Y coordinate of the point about which the object + * is being scaled, specified as an absolute number where 0 is the + * top edge. (This point remains fixed while the object changes + * size.) This value can either be an absolute number if pivotYType + * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise. + */ + public ScaleAnimation(float fromX, float toX, float fromY, float toY, + int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) { + mFromX = fromX; + mToX = toX; + mFromY = fromY; + mToY = toY; + + mPivotXValue = pivotXValue; + mPivotXType = pivotXType; + mPivotYValue = pivotYValue; + mPivotYType = pivotYType; + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float sx = 1.0f; + float sy = 1.0f; + + if (mFromX != 1.0f || mToX != 1.0f) { + sx = mFromX + ((mToX - mFromX) * interpolatedTime); + } + if (mFromY != 1.0f || mToY != 1.0f) { + sy = mFromY + ((mToY - mFromY) * interpolatedTime); + } + + if (mPivotX == 0 && mPivotY == 0) { + t.getMatrix().setScale(sx, sy); + } else { + t.getMatrix().setScale(sx, sy, mPivotX, mPivotY); + } + } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + + mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth); + mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight); + } +} diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java new file mode 100644 index 0000000..c7a0cc8 --- /dev/null +++ b/core/java/android/view/animation/Transformation.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.graphics.Matrix; + +/** + * Defines the transformation to be applied at + * one point in time of an Animation. + * + */ +public class Transformation { + /** + * Indicates a transformation that has no effect (alpha = 1 and identity matrix.) + */ + public static int TYPE_IDENTITY = 0x0; + /** + * Indicates a transformation that applies an alpha only (uses an identity matrix.) + */ + public static int TYPE_ALPHA = 0x1; + /** + * Indicates a transformation that applies a matrix only (alpha = 1.) + */ + public static int TYPE_MATRIX = 0x2; + /** + * Indicates a transformation that applies an alpha and a matrix. + */ + public static int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX; + + protected Matrix mMatrix; + protected float mAlpha; + protected int mTransformationType; + + /** + * Creates a new transformation with alpha = 1 and the identity matrix. + */ + public Transformation() { + clear(); + } + + /** + * Reset the transformation to a state that leaves the object + * being animated in an unmodified state. The transformation type is + * {@link #TYPE_BOTH} by default. + */ + public void clear() { + if (mMatrix == null) { + mMatrix = new Matrix(); + } else { + mMatrix.reset(); + } + mAlpha = 1.0f; + mTransformationType = TYPE_BOTH; + } + + /** + * Indicates the nature of this transformation. + * + * @return {@link #TYPE_ALPHA}, {@link #TYPE_MATRIX}, + * {@link #TYPE_BOTH} or {@link #TYPE_IDENTITY}. + */ + public int getTransformationType() { + return mTransformationType; + } + + /** + * Sets the transformation type. + * + * @param transformationType One of {@link #TYPE_ALPHA}, + * {@link #TYPE_MATRIX}, {@link #TYPE_BOTH} or + * {@link #TYPE_IDENTITY}. + */ + public void setTransformationType(int transformationType) { + mTransformationType = transformationType; + } + + /** + * Clones the specified transformation. + * + * @param t The transformation to clone. + */ + public void set(Transformation t) { + mAlpha = t.getAlpha(); + mMatrix.set(t.getMatrix()); + mTransformationType = t.getTransformationType(); + } + + /** + * Apply this Transformation to an existing Transformation, e.g. apply + * a scale effect to something that has already been rotated. + * @param t + */ + public void compose(Transformation t) { + mAlpha *= t.getAlpha(); + mMatrix.preConcat(t.getMatrix()); + } + + /** + * @return The 3x3 Matrix representing the trnasformation to apply to the + * coordinates of the object being animated + */ + public Matrix getMatrix() { + return mMatrix; + } + + /** + * Sets the degree of transparency + * @param alpha 1.0 means fully opaqe and 0.0 means fully transparent + */ + public void setAlpha(float alpha) { + mAlpha = alpha; + } + + /** + * @return The degree of transparency + */ + public float getAlpha() { + return mAlpha; + } + + @Override + public String toString() { + return "Transformation{alpha=" + mAlpha + " matrix=" + mMatrix + "}"; + } +} diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java new file mode 100644 index 0000000..ae21768 --- /dev/null +++ b/core/java/android/view/animation/TranslateAnimation.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2006 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.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +/** + * An animation that controls the position of an object. See the + * {@link android.view.animation full package} description for details and + * sample code. + * + */ +public class TranslateAnimation extends Animation { + private int mFromXType = ABSOLUTE; + private int mToXType = ABSOLUTE; + + private int mFromYType = ABSOLUTE; + private int mToYType = ABSOLUTE; + + private float mFromXValue = 0.0f; + private float mToXValue = 0.0f; + + private float mFromYValue = 0.0f; + private float mToYValue = 0.0f; + + private float mFromXDelta; + private float mToXDelta; + private float mFromYDelta; + private float mToYDelta; + + /** + * Constructor used whan an ScaleAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public TranslateAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.TranslateAnimation); + + Description d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.TranslateAnimation_fromXDelta)); + mFromXType = d.type; + mFromXValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.TranslateAnimation_toXDelta)); + mToXType = d.type; + mToXValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.TranslateAnimation_fromYDelta)); + mFromYType = d.type; + mFromYValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.TranslateAnimation_toYDelta)); + mToYType = d.type; + mToYValue = d.value; + + a.recycle(); + } + + /** + * Constructor to use when building a ScaleAnimation from code + * + * @param fromXDelta Change in X coordinate to apply at the start of the + * animation + * @param toXDelta Change in X coordinate to apply at the end of the + * animation + * @param fromYDelta Change in Y coordinate to apply at the start of the + * animation + * @param toYDelta Change in Y coordinate to apply at the end of the + * animation + */ + public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) { + mFromXValue = fromXDelta; + mToXValue = toXDelta; + mFromYValue = fromYDelta; + mToYValue = toYDelta; + + mFromXType = ABSOLUTE; + mToXType = ABSOLUTE; + mFromYType = ABSOLUTE; + mToYType = ABSOLUTE; + } + + /** + * Constructor to use when building a ScaleAnimation from code + * + * @param fromXType Specifies how fromXValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param fromXValue Change in X coordinate to apply at the start of the + * animation. This value can either be an absolute number if fromXType + * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise. + * @param toXType Specifies how toXValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param toXValue Change in X coordinate to apply at the end of the + * animation. This value can either be an absolute number if toXType + * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise. + * @param fromYType Specifies how fromYValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param fromYValue Change in Y coordinate to apply at the start of the + * animation. This value can either be an absolute number if fromYType + * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise. + * @param toYType Specifies how toYValue should be interpreted. One of + * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or + * Animation.RELATIVE_TO_PARENT. + * @param toYValue Change in Y coordinate to apply at the end of the + * animation. This value can either be an absolute number if toYType + * is ABSOLUTE, or a percentage (where 1.0 is 100%) otherwise. + */ + public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, + int fromYType, float fromYValue, int toYType, float toYValue) { + + mFromXValue = fromXValue; + mToXValue = toXValue; + mFromYValue = fromYValue; + mToYValue = toYValue; + + mFromXType = fromXType; + mToXType = toXType; + mFromYType = fromYType; + mToYType = toYType; + } + + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float dx = mFromXDelta; + float dy = mFromYDelta; + if (mFromXDelta != mToXDelta) { + dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime); + } + if (mFromYDelta != mToYDelta) { + dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime); + } + + t.getMatrix().setTranslate(dx, dy); + } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth); + mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth); + mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight); + mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight); + } +} diff --git a/core/java/android/view/animation/package.html b/core/java/android/view/animation/package.html new file mode 100755 index 0000000..c358047 --- /dev/null +++ b/core/java/android/view/animation/package.html @@ -0,0 +1,244 @@ +<html> +<body> +<p>Provides classes that handle tweened animations.</p> +<p>Android provides two mechanisms + that you can use to create simple animations: <strong>tweened + animation</strong>, in which you tell Android to perform a series of simple + transformations (position, size, rotation, and so on) to the content of a + View; and <strong>frame + by frame animation</strong>, which loads a series of Drawable resources + one after the other. Both animation types can be used in any View object + to provide simple rotating timers, activity icons, and other useful UI elements. + Tweened animation is handled by this package; frame by frame animation is + handled by the {@link android.graphics.drawable.AnimationDrawable} class. + Animations do not have a pause method.</p> +<h2>Tweened Animation<a name="tweened"></a></h2> +<p> Android can perform simple visual transformations for you, including straight + line motion, size change, and transparency change, on the contents of a {@link + android.view.View View} object. These transformations are represented by the + following classes:</p> +<ul> + <li> {@link android.view.animation.AlphaAnimation AlphaAnimation} (transparency + changes) </li> + <li>{@link android.view.animation.RotateAnimation RotateAnimation} (rotations) </li> + <li> {@link android.view.animation.ScaleAnimation ScaleAnimation} (growing + or shrinking) </li> + <li>{@link android.view.animation.TranslateAnimation TranslateAnimation} + (position changes) </li> +</ul> +<p><em>Note: tweened animation does not provide tools to help you draw shapes.</em> Tweened + animation is the act of applying one or more of these + transformations applied to the contents of a View object. So, if you have a TextView + with text, you can move, rotate, grow, or shrink the text. If it has a background + image, the background image will also be transformed along with the text. </p> +<p>Animations are drawn in the area designated for the View at the start of the animation; + this area does not change to accommodate size or movement, so if your animation + moves or expands outside the original boundaries of your object, it will be clipped + to the size of the original canvas, even if the object's LayoutParams are + set to WRAP_CONTENT (the object will not resize to accommodate moving or expanding/shrinking + animations).</p> +<h3>Step 1: Define your animation </h3> +<p>The first step in creating a tweened animation is to define the transformations. + This can be done either in XML or in code. You define an animation by defining + the transformations that you want to occur, when they will occur, and how long + they should take to apply. Transformations + can be sequential or simultaneous—so, for example, you can have the contents + of a TextView move from left to right, and then rotate 180 degrees, or you can + have the text move and rotate simultaneously. Each transformation takes a set + of parameters specific for that transformation (starting size and ending size + for size change, starting angle and ending angle for rotation, and so on), and + also a set of common parameters (for instance, start time and duration). To make + several transformations happen simultaneously, give them the same start time; + to make them sequential, calculate the start time plus the duration of the preceding + transformation. </p> +<p>Screen coordinates are (0,0) at the upper left hand corner, and increase as you + go down and to the right. </p> +<p>Some values, such as pivotX, can be specified relative to the object itself or + relative to the parent. Be sure to use the proper format for what you want ("50" + for 50% relative to the parent, "50%" for 50% relative to itself).</p> +<p>You can determine how a transformation is applied over time by assigning an Interpolator + to it. Android includes several Interpolator subclasses that specify various + speed curves: for instance, AccelerateInterpolator tells a transformation + to start slow and speed up; DecelerateInterpolator tells it to start fast than slow + down, and so on. </p> +<p>If + you want a group of transformations to share a set of parameters (for example, + start time and duration), you can bundle them into an AnimationSet, which + defines the common parameters for all its children (and overrides any + values explicitly set by the children). Add your AnimationSet as + a child to the root AnimationSet (which serves to wrap all transformations into + the final animation). </p> +<p> Here is the XML that defines a simple animation. The object will first move + to the right, then rotate and double in size, then move up. Note the + transformation start times.</p> +<table width="100%" border="1"> + <tr> + <th scope="col">XML</th> + <th scope="col">Equivalent Java </th> + </tr> + <tr> + <td><pre><set android:shareInterpolator="true" + android:interpolator="@android:anim/accelerate_interpolator"> + + <translate android:fromXDelta="0" + android:toXDelta="30" + android:duration="800" + android:fillAfter="true"/> + + <set android:duration="800" + android:pivotX="50%" + android:pivotY="50%" > + + <rotate android:fromDegrees="0" + android:toDegrees="-90" + android:fillAfter="true" + android:startOffset="800"/> + + <scale android:fromXScale="1.0" + android:toXScale="2.0" + android:fromYScale="1.0" + android:toYScale="2.0" + android:startOffset="800" /> + </set> + + <translate android:toYDelta="-100" + android:fillAfter="true" + android:duration="800" + android:startOffset="1600"/> +</set></pre></td> + <td><pre>// Create root AnimationSet. +AnimationSet rootSet = new AnimationSet(true); +rootSet.setInterpolator(new AccelerateInterpolator()); +rootSet.setRepeatMode(Animation.NO_REPEAT); + +// Create and add first child, a motion animation. +TranslateAnimation trans1 = new TranslateAnimation(0, 30, 0, 0); +trans1.setStartOffset(0); +trans1.setDuration(800); +trans1.setFillAfter(true); +rootSet.addAnimation(trans1); + +// Create a rotate and a size animation. +RotateAnimation rotate = new RotateAnimation( + 0, + -90, + RotateAnimation.RELATIVE_TO_SELF, 0.5f, + RotateAnimation.RELATIVE_TO_SELF, 0.5f); + rotate.setFillAfter(true); + rotate.setDuration(800); + +ScaleAnimation scale = new ScaleAnimation( + 1, 2, 1, 2, // From x, to x, from y, to y + ScaleAnimation.RELATIVE_TO_SELF, 0.5f, + ScaleAnimation.RELATIVE_TO_SELF, 0.5f); + scale.setDuration(800); + scale.setFillAfter(true); + +// Add rotate and size animations to a new set, +// then add the set to the root set. +AnimationSet childSet = new AnimationSet(true); +childSet.setStartOffset(800); +childSet.addAnimation(rotate); +childSet.addAnimation(scale); +rootSet.addAnimation(childSet); + +// Add a final motion animation to the root set. +TranslateAnimation trans2 = new TranslateAnimation(0, 0, 0, -100); +trans2.setFillAfter(true); +trans2.setDuration(800); +trans2.setStartOffset(1600); +rootSet.addAnimation(trans2); + +// Start the animation. +animWindow.startAnimation(rootSet);</pre></td> + </tr> +</table> +<p> </p> +<p>The following diagram shows the animation drawn from this code: </p> +<p><img src="{@docRoot}images/tweening_example.png" alt="A tweened animation: move right, turn and grow, move up." ></p> +<p>The previous diagram shows a few important things. One is the animation itself, + and the other is that the animation can get cropped if it moves out of its originally + defined area. To avoid this, we could have sized the TextView to fill_parent + for its height. </p> +<p>If you define your animation in XML, save it in the res/anim/ folder as described + in <a href="{@docRoot}reference/available-resources.html#tweenedanimation">Resources</a>. That topic + also describes the XML tags and attributes you can use to specify transformations. </p> +<p>Animations + have the following common parameters (from the Animation interface). + If a group of animations share the same values, you can bundle them into an AnimationSet + so you don't have to set these values on each one individually.</p> +<table width="100%" border="1"> + <tr> + <th scope="col">Property</th> + <th scope="col">XML Attribute</th> + <th scope="col">Java Method / </th> + <th scope="col">Description</th> + </tr> + <tr> + <td>Start time </td> + <td><code>android:startOffset</code></td> + <td><code>Animation.setStartOffset()</code> (or <code>setStartTime()</code> for absolute time)</td> + <td>The start time (in milliseconds) of a transformation, where 0 is the + start time of the root animation set. </td> + </tr> + <tr> + <td>Duration</td> + <td><code>android:duration</code></td> + <td><code>Animation.setDuration()</code></td> + <td>The duration (in milliseconds) of a transformation. </td> + </tr> + <tr> + <td>Fill before </td> + <td><code>android:fillBefore</code></td> + <td><code>Animation.setFillBefore()</code></td> + <td>True if you want this transformation to be applied at time zero, regardless + of your start time value (you will probably never need this). </td> + </tr> + <tr> + <td>Fill after </td> + <td><code>android:fillAfter</code></td> + <td><code>Animation.SetFillAfter()</code></td> + <td>Whether you want the transform you apply to continue after the duration + of the transformation has expired. If false, the original value will + immediately be applied when the transformation is done. So, for example, + if you want to make a dot move down, then right in an "L" shape, if this + value is not true, at the end of the down motion the text box will immediately + jump back to the top before moving right. </td> + </tr> + <tr> + <td>Interpolator</td> + <td><code>android:interpolator</code></td> + <td><code>Animation.SetInterpolator()</code></td> + <td>Which interpolator to use. </td> + </tr> + <tr> + <td>Repeat mode </td> + <td>Cannot be set in XML </td> + <td><code>Animation.SetRepeatMode()</code></td> + <td>Whether and how the animation should repeat. </td> + </tr> +</table> +<p> </p> +<h3>Step 2: Load and start your animation </h3> +<ol> + <li>If you've created your transformation in XML, you'll need to load it in Java + by calling {@link android.view.animation.AnimationUtils#loadAnimation(android.content.Context, + int) AnimationUtils.loadAnimation()}. </li> + <li>Either start the animation immediately by calling {@link android.view.View#startAnimation(android.view.animation.Animation) + View.startAnimation()}, or if you have specified a start time in the animation + parameters, you can call + {@link android.view.View#setAnimation(android.view.animation.Animation) + View.setCurrentAnimation()}.</li> +</ol> +<p>The following code demonstrates loading and starting an animation. </p> +<pre>// Hook into the object to be animated. +TextView animWindow = (TextView)findViewById(R.id.anim); + +// Load the animation from XML (XML file is res/anim/move_animation.xml). +Animation anim = AnimationUtils.loadAnimation(AnimationSample.this, R.anim.move_animation); +anim.setRepeatMode(Animation.NO_REPEAT); + +// Play the animation. +animWindow.startAnimation(anim);</pre> +</body> +</html> diff --git a/core/java/android/view/package.html b/core/java/android/view/package.html new file mode 100644 index 0000000..1c58765 --- /dev/null +++ b/core/java/android/view/package.html @@ -0,0 +1,6 @@ +<HTML> +<BODY> +Provides classes that expose basic user interface classes that handle +screen layout and interaction with the user. +</BODY> +</HTML>
\ No newline at end of file |