diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
commit | 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch) | |
tree | d88beb88001f2482911e3d28e43833b50e4b4e97 /core/java/android/view | |
parent | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff) | |
download | frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/android/view')
90 files changed, 36708 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..dd1d7db --- /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 and item icons. + * <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..15fb839 --- /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.get(root.mContext).getScaledEdgeSlop(); + + 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..e0231a7 --- /dev/null +++ b/core/java/android/view/GestureDetector.java @@ -0,0 +1,564 @@ +/* + * 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; +import android.content.Context; + +/** + * 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); + } + + /** + * The listener that is used to notify when a double-tap or a confirmed + * single-tap occur. + */ + public interface OnDoubleTapListener { + /** + * Notified when a single-tap occurs. + * <p> + * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this + * will only be called after the detector is confident that the user's + * first tap is not followed by a second tap leading to a double-tap + * gesture. + * + * @param e The down motion event of the single-tap. + * @return true if the event is consumed, else false + */ + boolean onSingleTapConfirmed(MotionEvent e); + + /** + * Notified when a double-tap occurs. + * + * @param e The down motion event of the first tap of the double-tap. + * @return true if the event is consumed, else false + */ + boolean onDoubleTap(MotionEvent e); + + /** + * Notified when an event within a double-tap gesture occurs, including + * the down, move, and up events. + * + * @param e The motion event that occurred during the double-tap gesture. + * @return true if the event is consumed, else false + */ + boolean onDoubleTapEvent(MotionEvent e); + } + + /** + * 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} and {@link OnDoubleTapListener} but does + * nothing and return {@code false} for all applicable methods. + */ + public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener { + 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; + } + + public boolean onDoubleTap(MotionEvent e) { + return false; + } + + public boolean onDoubleTapEvent(MotionEvent e) { + return false; + } + + public boolean onSingleTapConfirmed(MotionEvent e) { + return false; + } + } + + // TODO: ViewConfiguration + private int mBiggerTouchSlopSquare = 20 * 20; + + private int mTouchSlopSquare; + private int mDoubleTapSlopSquare; + private int mMinimumFlingVelocity; + + private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); + private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); + // TODO make new double-tap timeout, and define its events (i.e. either time + // between down-down or time between up-down) + private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); + + // constants for Message.what used by GestureHandler below + private static final int SHOW_PRESS = 1; + private static final int LONG_PRESS = 2; + private static final int TAP = 3; + + private final Handler mHandler; + private final OnGestureListener mListener; + private OnDoubleTapListener mDoubleTapListener; + + private boolean mStillDown; + private boolean mInLongPress; + private boolean mAlwaysInTapRegion; + private boolean mAlwaysInBiggerTapRegion; + + private MotionEvent mCurrentDownEvent; + private MotionEvent mPreviousUpEvent; + + /** + * True when the user is still touching for the second tap (down, move, and + * up events). Can only be true if there is a double tap listener attached. + */ + private boolean mIsDoubleTapping; + + 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; + + case TAP: + // If the user's finger is still down, do not count it as a tap + if (mDoubleTapListener != null && !mStillDown) { + mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); + } + 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 + * + * @throws NullPointerException if either {@code listener} or + * {@code handler} is null. + * + * @deprecated Use {@link #GestureDetector(android.content.Context, + * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead. + */ + @Deprecated + public GestureDetector(OnGestureListener listener, Handler handler) { + this(null, listener, handler); + } + + /** + * 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. + * + * @deprecated Use {@link #GestureDetector(android.content.Context, + * android.view.GestureDetector.OnGestureListener)} instead. + */ + @Deprecated + public GestureDetector(OnGestureListener listener) { + this(null, listener, null); + } + + /** + * 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 context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * + * @throws NullPointerException if {@code listener} is null. + */ + public GestureDetector(Context context, OnGestureListener listener) { + this(context, listener, null); + } + + /** + * 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 context the application's context + * @param listener the listener invoked for all the callbacks, this must + * not be null. + * @param handler the handler to use + * + * @throws NullPointerException if {@code listener} is null. + */ + public GestureDetector(Context context, OnGestureListener listener, Handler handler) { + if (handler != null) { + mHandler = new GestureHandler(handler); + } else { + mHandler = new GestureHandler(); + } + mListener = listener; + if (listener instanceof OnDoubleTapListener) { + setOnDoubleTapListener((OnDoubleTapListener) listener); + } + init(context); + } + + private void init(Context context) { + if (mListener == null) { + throw new NullPointerException("OnGestureListener must not be null"); + } + mIsLongpressEnabled = true; + + // Fallback to support pre-donuts releases + int touchSlop, doubleTapSlop; + if (context == null) { + //noinspection deprecation + touchSlop = ViewConfiguration.getTouchSlop(); + doubleTapSlop = ViewConfiguration.getDoubleTapSlop(); + //noinspection deprecation + mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); + } else { + final ViewConfiguration configuration = ViewConfiguration.get(context); + touchSlop = configuration.getScaledTouchSlop(); + doubleTapSlop = configuration.getScaledDoubleTapSlop(); + mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); + } + mTouchSlopSquare = touchSlop * touchSlop; + mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop; + } + + /** + * Sets the listener which will be called for double-tap and related + * gestures. + * + * @param onDoubleTapListener the listener invoked for all the callbacks, or + * null to stop listening for double-tap gestures. + */ + public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) { + mDoubleTapListener = onDoubleTapListener; + } + + /** + * 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 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: + if (mDoubleTapListener != null) { + boolean hadTapMessage = mHandler.hasMessages(TAP); + if (hadTapMessage) mHandler.removeMessages(TAP); + if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage && + isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { + // This is a second tap + mIsDoubleTapping = true; + // Give a callback with the first tap of the double-tap + handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); + // Give a callback with down event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else { + // This is a first tap + mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT); + } + } + + mLastMotionX = x; + mLastMotionY = y; + mCurrentDownEvent = MotionEvent.obtain(ev); + mAlwaysInTapRegion = true; + mAlwaysInBiggerTapRegion = true; + mStillDown = true; + mInLongPress = false; + + if (mIsLongpressEnabled) { + mHandler.removeMessages(LONG_PRESS); + mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() + + TAP_TIMEOUT + LONGPRESS_TIMEOUT); + } + mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); + handled |= mListener.onDown(ev); + break; + + case MotionEvent.ACTION_MOVE: + if (mInLongPress) { + break; + } + final float scrollX = mLastMotionX - x; + final float scrollY = mLastMotionY - y; + if (mIsDoubleTapping) { + // Give the move events of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + } else if (mAlwaysInTapRegion) { + final int deltaX = (int) (x - mCurrentDownEvent.getX()); + final int deltaY = (int) (y - mCurrentDownEvent.getY()); + int distance = (deltaX * deltaX) + (deltaY * deltaY); + if (distance > mTouchSlopSquare) { + handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); + mLastMotionX = x; + mLastMotionY = y; + mAlwaysInTapRegion = false; + mHandler.removeMessages(TAP); + mHandler.removeMessages(SHOW_PRESS); + mHandler.removeMessages(LONG_PRESS); + } + if (distance > mBiggerTouchSlopSquare) { + mAlwaysInBiggerTapRegion = false; + } + } 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: + mStillDown = false; + MotionEvent currentUpEvent = MotionEvent.obtain(ev); + if (mIsDoubleTapping) { + // Finally, give the up event of the double-tap + handled |= mDoubleTapListener.onDoubleTapEvent(ev); + mIsDoubleTapping = false; + break; + } else if (mInLongPress) { + mHandler.removeMessages(TAP); + 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) > mMinimumFlingVelocity) + || (Math.abs(velocityX) > mMinimumFlingVelocity)){ + handled = mListener.onFling(mCurrentDownEvent, currentUpEvent, velocityX, velocityY); + } + } + mPreviousUpEvent = MotionEvent.obtain(ev); + 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); + mHandler.removeMessages(TAP); + mVelocityTracker.recycle(); + mVelocityTracker = null; + mStillDown = false; + if (mInLongPress) { + mInLongPress = false; + break; + } + } + return handled; + } + + private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp, + MotionEvent secondDown) { + if (!mAlwaysInBiggerTapRegion) { + return false; + } + + if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) { + return false; + } + + int deltaX = (int) firstDown.getX() - (int) secondDown.getX(); + int deltaY = (int) firstDown.getY() - (int) secondDown.getY(); + return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare); + } + + private void dispatchLongPress() { + mHandler.removeMessages(TAP); + 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..36d8ce6 --- /dev/null +++ b/core/java/android/view/Gravity.java @@ -0,0 +1,306 @@ +/* + * 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 +{ + /** Constant 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; + /** Raw bit controlling whether the right/bottom edge is clipped to its + * container, based on the gravity direction being applied. */ + public static final int AXIS_CLIP = 0x0008; + + /** 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 object if needed so it + * completely fills its container. */ + public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL; + + /** Flag to clip the edges of the object to its container along the + * vertical axis. */ + public static final int CLIP_VERTICAL = AXIS_CLIP<<AXIS_Y_SHIFT; + + /** Flag to clip the edges of the object to its container along the + * horizontal axis. */ + public static final int CLIP_HORIZONTAL = AXIS_CLIP<<AXIS_X_SHIFT; + + /** + * 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; + + /** Special constant to enable clipping to an overall display along the + * vertical dimension. This is not applied by default by + * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so + * yourself by calling {@link #applyDisplay}. + */ + public static final int DISPLAY_CLIP_VERTICAL = 0x10000000; + + /** Special constant to enable clipping to an overall display along the + * horizontal dimension. This is not applied by default by + * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so + * yourself by calling {@link #applyDisplay}. + */ + public static final int DISPLAY_CLIP_HORIZONTAL = 0x01000000; + + /** + * 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) { + switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) { + case 0: + outRect.left = container.left + + ((container.right - container.left - w)/2) + xAdj; + outRect.right = outRect.left + w; + if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT)) + == (AXIS_CLIP<<AXIS_X_SHIFT)) { + if (outRect.left < container.left) { + outRect.left = container.left; + } + if (outRect.right > container.right) { + outRect.right = container.right; + } + } + break; + case AXIS_PULL_BEFORE<<AXIS_X_SHIFT: + outRect.left = container.left + xAdj; + outRect.right = outRect.left + w; + if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT)) + == (AXIS_CLIP<<AXIS_X_SHIFT)) { + if (outRect.right > container.right) { + outRect.right = container.right; + } + } + break; + case AXIS_PULL_AFTER<<AXIS_X_SHIFT: + outRect.right = container.right - xAdj; + outRect.left = outRect.right - w; + if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT)) + == (AXIS_CLIP<<AXIS_X_SHIFT)) { + if (outRect.left < container.left) { + outRect.left = container.left; + } + } + break; + default: + outRect.left = container.left + xAdj; + outRect.right = container.right + xAdj; + break; + } + + switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) { + case 0: + outRect.top = container.top + + ((container.bottom - container.top - h)/2) + yAdj; + outRect.bottom = outRect.top + h; + if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT)) + == (AXIS_CLIP<<AXIS_Y_SHIFT)) { + if (outRect.top < container.top) { + outRect.top = container.top; + } + if (outRect.bottom > container.bottom) { + outRect.bottom = container.bottom; + } + } + break; + case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT: + outRect.top = container.top + yAdj; + outRect.bottom = outRect.top + h; + if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT)) + == (AXIS_CLIP<<AXIS_Y_SHIFT)) { + if (outRect.bottom > container.bottom) { + outRect.bottom = container.bottom; + } + } + break; + case AXIS_PULL_AFTER<<AXIS_Y_SHIFT: + outRect.bottom = container.bottom - yAdj; + outRect.top = outRect.bottom - h; + if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT)) + == (AXIS_CLIP<<AXIS_Y_SHIFT)) { + if (outRect.top < container.top) { + outRect.top = container.top; + } + } + break; + default: + outRect.top = container.top + yAdj; + outRect.bottom = container.bottom + yAdj; + break; + } + } + + /** + * Apply addition gravity behavior based on the overall "display" that an + * object exists in. This can be used after + * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object + * within a visible display. By default this moves or clips the object + * to be visible in the display; the gravity flags + * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL} + * can be used to change this behavior. + * + * @param gravity Gravity constants to modify the placement within the + * display. + * @param display The rectangle of the display in which the object is + * being placed. + * @param inoutObj Supplies the current object position; returns with it + * modified if needed to fit in the display. + */ + public static void applyDisplay(int gravity, Rect display, Rect inoutObj) { + if ((gravity&DISPLAY_CLIP_VERTICAL) != 0) { + if (inoutObj.top < display.top) inoutObj.top = display.top; + if (inoutObj.bottom > display.bottom) inoutObj.bottom = display.bottom; + } else { + int off = 0; + if (inoutObj.top < display.top) off = display.top-inoutObj.top; + else if (inoutObj.bottom > display.bottom) off = display.bottom-inoutObj.bottom; + if (off != 0) { + if (inoutObj.height() > (display.bottom-display.top)) { + inoutObj.top = display.top; + inoutObj.bottom = display.bottom; + } else { + inoutObj.top += off; + inoutObj.bottom += off; + } + } + } + + if ((gravity&DISPLAY_CLIP_HORIZONTAL) != 0) { + if (inoutObj.left < display.left) inoutObj.left = display.left; + if (inoutObj.right > display.right) inoutObj.right = display.right; + } else { + int off = 0; + if (inoutObj.left < display.left) off = display.left-inoutObj.left; + else if (inoutObj.right > display.right) off = display.right-inoutObj.right; + if (off != 0) { + if (inoutObj.width() > (display.right-display.left)) { + inoutObj.left = display.left; + inoutObj.right = display.right; + } else { + inoutObj.left += off; + inoutObj.right += off; + } + } + } + } + + /** + * <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; + } +} diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java new file mode 100644 index 0000000..cc3563c --- /dev/null +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +/** + * Constants to be used to perform haptic feedback effects via + * {@link View#performHapticFeedback(int)} + */ +public class HapticFeedbackConstants { + + private HapticFeedbackConstants() {} + + public static final int LONG_PRESS = 0; + + /** @hide pending API council */ + public static final int ZOOM_RING_TICK = 1; + + /** + * Flag for {@link View#performHapticFeedback(int, int) + * View.performHapticFeedback(int, int)}: Ignore the setting in the + * view for whether to perform haptic feedback, do it always. + */ + public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001; + + /** + * Flag for {@link View#performHapticFeedback(int, int) + * View.performHapticFeedback(int, int)}: Ignore the global setting + * for whether to perform haptic feedback, do it always. + */ + public static final int FLAG_IGNORE_GLOBAL_SETTING = 0x0002; +} 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..99d5c0c --- /dev/null +++ b/core/java/android/view/IWindow.aidl @@ -0,0 +1,59 @@ +/* //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.graphics.Rect; +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, in Rect coveredInsets, in Rect visibleInsets, + 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..a856b24 --- /dev/null +++ b/core/java/android/view/IWindowManager.aidl @@ -0,0 +1,136 @@ +/* //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 com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodClient; + +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(in IInputMethodClient client, + in IInputContext inputContext); + boolean inputMethodClientHasFocus(IInputMethodClient client); + + // 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); + int getPendingAppTransition(); + 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); + void addWindowToken(IBinder token, int type); + void removeWindowToken(IBinder token); + + // 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); + + // Report whether the hardware supports the given keys; returns true if successful + boolean hasKeys(in int[] keycodes, inout boolean[] keyExists); + + // 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..1156856 --- /dev/null +++ b/core/java/android/view/IWindowSession.aidl @@ -0,0 +1,111 @@ +/* //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 outContentInsets); + 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 insetsPending Set to true if the client will be later giving + * internal insets; as a result, the window will not impact other window + * layouts until the insets are given. + * @param outFrame Rect in which is placed the new position/size on + * screen. + * @param outContentInsets Rect in which is placed the offsets from + * <var>outFrame</var> in which the content of the window should be + * placed. This can be used to modify the window layout to ensure its + * contents are visible to the user, taking into account system windows + * like the status bar or a soft keyboard. + * @param outVisibleInsets Rect in which is placed the offsets from + * <var>outFrame</var> in which the window is actually completely visible + * to the user. This can be used to temporarily scroll the window's + * contents to make sure the user can see it. This is different than + * <var>outContentInsets</var> in that these insets change transiently, + * so complex relayout of the window should not happen based on them. + * @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, + boolean insetsPending, out Rect outFrame, out Rect outContentInsets, + out Rect outVisibleInsets, out Surface outSurface); + + /** + * Give the window manager a hint of the part of the window that is + * completely transparent, allowing it to work with the surface flinger + * to optimize compositing of this part of the window. + */ + void setTransparentRegion(IWindow window, in Region region); + + /** + * Tell the window manager about the content and visible insets of the + * given window, which can be used to adjust the <var>outContentInsets</var> + * and <var>outVisibleInsets</var> values returned by + * {@link #relayout relayout()} for windows behind this one. + * + * @param touchableInsets Controls which part of the window inside of its + * frame can receive pointer events, as defined by + * {@link android.view.ViewTreeObserver.InternalInsetsInfo}. + */ + void setInsets(IWindow window, int touchableInsets, in Rect contentInsets, + in Rect visibleInsets); + + /** + * Return the current display size in which the window is being laid out, + * accounting for screen decorations around it. + */ + void getDisplayFrame(IWindow window, out Rect outDisplayFrame); + + void finishDrawing(IWindow window); + + void finishKey(IWindow window); + MotionEvent getPendingPointerMove(IWindow window); + MotionEvent getPendingTrackballMove(IWindow window); + + void setInTouchMode(boolean showFocus); + boolean getInTouchMode(); + + boolean performHapticFeedback(IWindow window, int effectId, boolean always); +} 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..25958aa --- /dev/null +++ b/core/java/android/view/KeyCharacterMap.java @@ -0,0 +1,545 @@ +/* + * 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.RemoteException; +import android.os.ServiceManager; +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); + } + + /** + * Queries the framework about whether any physical keys exist on the + * device that are capable of producing the given key codes. + */ + public static boolean deviceHasKey(int keyCode) { + int[] codeArray = new int[1]; + codeArray[0] = keyCode; + boolean[] ret = deviceHasKeys(codeArray); + return ret[0]; + } + + public static boolean[] deviceHasKeys(int[] keyCodes) { + boolean[] ret = new boolean[keyCodes.length]; + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + try { + wm.hasKeys(keyCodes, ret); + } catch (RemoteException e) { + // no fallback; just return the empty array + } + return ret; + } + + 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..430cc71 --- /dev/null +++ b/core/java/android/view/KeyEvent.java @@ -0,0 +1,886 @@ +/* + * 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; + public static final int KEYCODE_PLAYPAUSE = 85; + public static final int KEYCODE_STOP = 86; + public static final int KEYCODE_NEXTSONG = 87; + public static final int KEYCODE_PREVIOUSSONG = 88; + public static final int KEYCODE_REWIND = 89; + public static final int KEYCODE_FORWARD = 90; + public static final int KEYCODE_MUTE = 91; + private static final int LAST_KEYCODE = KEYCODE_MUTE; + + // NOTE: If you add a new keycode here you must also add it to: + // isSystem() + // frameworks/base/include/ui/KeycodeLabels.h + // tools/puppet_master/PuppetMaster/nav_keys.py + // frameworks/base/core/res/res/values/attrs.xml + // commands/monkey/Monkey.java + // emulator? + + /** + * @deprecated There are now more than MAX_KEYCODE keycodes. + * Use {@link #getMaxKeyCode()} instead. + */ + @Deprecated + 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, or a complex string is being delivered. If the + * key code is not {#link {@link #KEYCODE_UNKNOWN} then the + * {#link {@link #getRepeatCount()} method returns the number of times + * the given key code should be executed. + * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then + * this is a sequence of characters as returned by {@link #getCharacters}. + */ + 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; + + /** + * This mask is set if the key event was generated by a software keyboard. + */ + public static final int FLAG_SOFT_KEYBOARD = 0x2; + + /** + * This mask is set if we don't want the key event to cause us to leave + * touch mode. + */ + public static final int FLAG_KEEP_TOUCH_MODE = 0x4; + + /** + * Returns the maximum keycode. + */ + public static int getMaxKeyCode() { + return LAST_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 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; + private String mCharacters; + + 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; + } + + /** + * Create a new key event for a string of characters. The key code, + * action, and repeat could will automatically be set to + * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, and 0 for you. + * + * @param time The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this event occured. + * @param characters The string of characters. + * @param device The device ID that generated the key event. + * @param flags The flags for this key event + */ + public KeyEvent(long time, String characters, int device, int flags) { + mDownTime = time; + mEventTime = time; + mCharacters = characters; + mAction = ACTION_MULTIPLE; + mKeyCode = KEYCODE_UNKNOWN; + mRepeatCount = 0; + mDeviceId = device; + 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; + mCharacters = origEvent.mCharacters; + } + + /** + * Copy an existing key event, modifying its action. + * + * @param origEvent The existing event to be copied. + * @param action The new action code of the event. + */ + public KeyEvent(KeyEvent origEvent, int action) { + mDownTime = origEvent.mDownTime; + mEventTime = origEvent.mEventTime; + mAction = action; + mKeyCode = origEvent.mKeyCode; + mRepeatCount = origEvent.mRepeatCount; + mMetaState = origEvent.mMetaState; + mDeviceId = origEvent.mDeviceId; + mScancode = origEvent.mScancode; + mFlags = origEvent.mFlags; + // Don't copy mCharacters, since one way or the other we'll lose it + // when changing the action. + } + + /** + * 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_MUTE: + case KEYCODE_POWER: + case KEYCODE_HEADSETHOOK: + case KEYCODE_PLAYPAUSE: + case KEYCODE_STOP: + case KEYCODE_NEXTSONG: + case KEYCODE_PREVIOUSSONG: + case KEYCODE_REWIND: + case KEYCODE_FORWARD: + 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, <em>not</em> the Unicode character. + * + * @return The key code of the event. + */ + public final int getKeyCode() { + return mKeyCode; + } + + /** + * For the special case of a {@link #ACTION_MULTIPLE} event with key + * code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters + * associated with the event. In all other cases it is null. + * + * @return Returns a String of 1 or more characters associated with + * the event. + */ + public final String getCharacters() { + return mCharacters; + } + + /** + * 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; + } + if (code != KeyEvent.KEYCODE_UNKNOWN) { + 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..97825e6 --- /dev/null +++ b/core/java/android/view/Menu.java @@ -0,0 +1,441 @@ +/* + * 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 and item icons. + * <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(); + + /** + * Gets the menu item at the given index. + * + * @param index The index of the menu item to return. + * @return The menu item. + * @exception IndexOutOfBoundsException + * when {@code index < 0 || >= size()} + */ + public MenuItem getItem(int index); + + /** + * Closes the menu, if open. + */ + public void close(); + + /** + * 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..fcebec5 --- /dev/null +++ b/core/java/android/view/MenuItem.java @@ -0,0 +1,384 @@ +/* + * 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.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..882a079 --- /dev/null +++ b/core/java/android/view/MotionEvent.java @@ -0,0 +1,685 @@ +/* + * 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 { + /** + * Constant for {@link #getAction}: A pressed gesture has started, the + * motion contains the initial starting location. + */ + public static final int ACTION_DOWN = 0; + /** + * Constant for {@link #getAction}: A pressed gesture has finished, the + * motion contains the final release location as well as any intermediate + * points since the last down or move event. + */ + public static final int ACTION_UP = 1; + /** + * Constant for {@link #getAction}: A change has happened during a + * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}). + * The motion contains the most recent point, as well as any intermediate + * points since the last down or move event. + */ + public static final int ACTION_MOVE = 2; + /** + * Constant for {@link #getAction}: The current gesture has been aborted. + * You will not receive any more points in it. You should treat this as + * an up event, but not perform any action that you normally would. + */ + public static final int ACTION_CANCEL = 3; + /** + * Constant for {@link #getAction}: A movement has happened outside of the + * normal bounds of the UI element. This does not provide a full gesture, + * but only the initial location of the movement/touch. + */ + public static final int ACTION_OUTSIDE = 4; + + 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/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java new file mode 100755 index 0000000..391ba1e --- /dev/null +++ b/core/java/android/view/OrientationEventListener.java @@ -0,0 +1,174 @@ +/* + * 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.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +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 OrientationEventListener { + private static final String TAG = "OrientationEventListener"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private int mOrientation = ORIENTATION_UNKNOWN; + private SensorManager mSensorManager; + private boolean mEnabled = false; + private int mRate; + private Sensor mSensor; + private SensorEventListener mSensorEventListener; + private OrientationListener mOldListener; + + /** + * 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 OrientationEventListener. + * + * @param context for the OrientationEventListener. + */ + public OrientationEventListener(Context context) { + this(context, SensorManager.SENSOR_DELAY_NORMAL); + } + + /** + * Creates a new OrientationEventListener. + * + * @param context for the OrientationEventListener. + * @param rate at which sensor events are processed (see also + * {@link android.hardware.SensorManager SensorManager}). Use the default + * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL + * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. + */ + public OrientationEventListener(Context context, int rate) { + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mRate = rate; + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (mSensor != null) { + // Create listener only if sensors do exist + mSensorEventListener = new SensorEventListenerImpl(); + } + } + + void registerListener(OrientationListener lis) { + mOldListener = lis; + } + + /** + * Enables the OrientationEventListener so it will monitor the sensor and call + * {@link #onOrientationChanged} when the device orientation changes. + */ + public void enable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Not enabled"); + return; + } + if (mEnabled == false) { + if (localLOGV) Log.d(TAG, "OrientationEventListener enabled"); + mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); + mEnabled = true; + } + } + + /** + * Disables the OrientationEventListener. + */ + public void disable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Invalid disable"); + return; + } + if (mEnabled == true) { + if (localLOGV) Log.d(TAG, "OrientationEventListener disabled"); + mSensorManager.unregisterListener(mSensorEventListener); + mEnabled = false; + } + } + + class SensorEventListenerImpl implements SensorEventListener { + private static final int _DATA_X = 0; + private static final int _DATA_Y = 1; + private static final int _DATA_Z = 2; + + public void onSensorChanged(SensorEvent event) { + float[] values = event.values; + int orientation = ORIENTATION_UNKNOWN; + float X = -values[_DATA_X]; + float Y = -values[_DATA_Y]; + float Z = -values[_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 (mOldListener != null) { + mOldListener.onSensorChanged(Sensor.TYPE_ACCELEROMETER, event.values); + } + if (orientation != mOrientation) { + mOrientation = orientation; + onOrientationChanged(orientation); + } + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + } + + /* + * Returns true if sensor is enabled and false otherwise + */ + public boolean canDetectOrientation() { + return mSensor != null; + } + + /** + * 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/OrientationListener.java b/core/java/android/view/OrientationListener.java new file mode 100644 index 0000000..ce8074e --- /dev/null +++ b/core/java/android/view/OrientationListener.java @@ -0,0 +1,110 @@ +/* + * 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; + +/** + * Helper class for receiving notifications from the SensorManager when + * the orientation of the device has changed. + * @deprecated use {@link android.view.OrientationEventListener} instead. + * This class internally uses the OrientationEventListener. + */ +@Deprecated +public abstract class OrientationListener implements SensorListener { + private OrientationEventListener mOrientationEventLis; + + /** + * 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 = OrientationEventListener.ORIENTATION_UNKNOWN; + + /** + * Creates a new OrientationListener. + * + * @param context for the OrientationListener. + */ + public OrientationListener(Context context) { + mOrientationEventLis = new OrientationEventListenerInternal(context); + } + + /** + * Creates a new OrientationListener. + * + * @param context for the OrientationListener. + * @param rate at which sensor events are processed (see also + * {@link android.hardware.SensorManager SensorManager}). Use the default + * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL + * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. + */ + public OrientationListener(Context context, int rate) { + mOrientationEventLis = new OrientationEventListenerInternal(context, rate); + } + + class OrientationEventListenerInternal extends OrientationEventListener { + OrientationEventListenerInternal(Context context) { + super(context); + } + + OrientationEventListenerInternal(Context context, int rate) { + super(context, rate); + // register so that onSensorChanged gets invoked + registerListener(OrientationListener.this); + } + + public void onOrientationChanged(int orientation) { + OrientationListener.this.onOrientationChanged(orientation); + } + } + + /** + * Enables the OrientationListener so it will monitor the sensor and call + * {@link #onOrientationChanged} when the device orientation changes. + */ + public void enable() { + mOrientationEventLis.enable(); + } + + /** + * Disables the OrientationListener. + */ + public void disable() { + mOrientationEventLis.disable(); + } + + public void onAccuracyChanged(int sensor, int accuracy) { + } + + public void onSensorChanged(int sensor, float[] values) { + // just ignore the call here onOrientationChanged is invoked anyway + } + + + /** + * Look at {@link android.view.OrientationEventListener#onOrientationChanged} + * for method description and usage + * @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..30da83e --- /dev/null +++ b/core/java/android/view/RawInputEvent.java @@ -0,0 +1,170 @@ +/** + * + */ +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_ALPHAKEY = 0x00000002; + public static final int CLASS_TOUCHSCREEN = 0x00000004; + public static final int CLASS_TRACKBALL = 0x00000008; + + // 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/RemotableViewMethod.java b/core/java/android/view/RemotableViewMethod.java new file mode 100644 index 0000000..4318290 --- /dev/null +++ b/core/java/android/view/RemotableViewMethod.java @@ -0,0 +1,35 @@ +/* + * 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @hide + * This annotation indicates that a method on a subclass of View + * is alllowed to be used with the {@link android.widget.RemoteViews} mechanism. + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface RemotableViewMethod { +} + + + 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..3d0dda3 --- /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 #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..e928998 --- /dev/null +++ b/core/java/android/view/SurfaceView.java @@ -0,0 +1,614 @@ +/* + * 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; +import java.lang.ref.WeakReference; + +/** + * 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 int[] mLocation = new int[2]; + + 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 mVisibleInsets = new Rect(); + final Rect mWinFrame = new Rect(); + final Rect mContentInsets = 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(this); + mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; + mLayout.gravity = Gravity.LEFT|Gravity.TOP; + mSession.add(mWindow, mLayout, + mVisible ? VISIBLE : GONE, mContentInsets); + } + + if (visibleChanged && (!visible || mNewSurfaceNeeded)) { + reportSurfaceDestroyed(); + } + + mNewSurfaceNeeded = false; + + mSurfaceLock.lock(); + mDrawingStopped = !visible; + final int relayoutResult = mSession.relayout( + mWindow, mLayout, mWidth, mHeight, + visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets, + mVisibleInsets, 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 static class MyWindow extends IWindow.Stub { + private WeakReference<SurfaceView> mSurfaceView; + + public MyWindow(SurfaceView surfaceView) { + mSurfaceView = new WeakReference<SurfaceView>(surfaceView); + } + + public void resized(int w, int h, Rect coveredInsets, + Rect visibleInsets, boolean reportDraw) { + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + if (localLOGV) Log.v( + "SurfaceView", surfaceView + " 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 { + surfaceView.mSession.finishDrawing(surfaceView.mWindow); + } catch (RemoteException e) { + } + } + } + } + } + + public void dispatchKey(KeyEvent event) { + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + //Log.w("SurfaceView", "Unexpected key event in surface: " + event); + if (surfaceView.mSession != null && surfaceView.mSurface != null) { + try { + surfaceView.mSession.finishKey(surfaceView.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() { + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG); + surfaceView.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..27b49db --- /dev/null +++ b/core/java/android/view/TouchDelegate.java @@ -0,0 +1,153 @@ +/* + * 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; + + private int mSlop; + + /** + * 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; + + mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop(); + mSlopBounds = new Rect(bounds); + mSlopBounds.inset(-mSlop, -mSlop); + 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 = mSlop; + 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..3e762f5 --- /dev/null +++ b/core/java/android/view/View.java @@ -0,0 +1,8076 @@ +/* + * 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.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +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.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.EditorInfo; +import android.widget.ScrollBarDrawable; + +import com.android.internal.R; +import com.android.internal.view.menu.MenuBuilder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.lang.ref.SoftReference; + +/** + * <p> + * This class represents the basic building block for user interface components. A View + * occupies a rectangular area on the screen and is responsible for drawing and + * event handling. View is the base class for <em>widgets</em>, which are + * used to create interactive UI components (buttons, text fields, etc.). The + * {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which + * are invisible containers that hold other Views (or other ViewGroups) and define + * their layout properties. + * </p> + * + * <div class="special"> + * <p>For an introduction to using this class to develop your + * application's user interface, read the Developer Guide documentation on + * <strong><a href="{@docRoot}guide/topics/ui/index.html">User Interface</a></strong>. Special topics + * include: + * <br/><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Declaring Layout</a> + * <br/><a href="{@docRoot}guide/topics/ui/menus.html">Creating Menus</a> + * <br/><a href="{@docRoot}guide/topics/ui/layout-objects.html">Common Layout Objects</a> + * <br/><a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a> + * <br/><a href="{@docRoot}guide/topics/ui/ui-events.html">Handling UI Events</a> + * <br/><a href="{@docRoot}guide/topics/ui/themes.html">Applying Styles and Themes</a> + * <br/><a href="{@docRoot}guide/topics/ui/custom-components.html">Building Custom Components</a> + * <br/><a href="{@docRoot}guide/topics/ui/how-android-draws.html">How Android Draws Views</a>. + * </p> + * </div> + * + * <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. + * If you set a background drawable for a View, then the View will draw it for you + * before calling back to its <code>onDraw()</code> method. + * </p> + * + * <p> + * Note that the framework will not draw views that are not in the invalid region. + * </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; + + /** + * View flag indicating whether this view should have haptic feedback + * enabled for events such as long presses. + */ + public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000; + + /** + * 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}; + + /** + * Temporary Rect currently for use in setBackground(). This will probably + * be extended in the future to hold our own class with more than just + * a Rect. :) + */ + static final ThreadLocal<Rect> sThreadLocal = new ThreadLocal<Rect>(); + + /** + * 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; + + /** + * 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; + /** + * When this flag is set, this view is running an animation on behalf of its + * children and should therefore not cancel invalidate requests, even if they + * lie outside of this view's bounds. + * + * {@hide} + */ + static final int DRAW_ANIMATION = 0x00000040; + /** {@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; + + /** + * Set by {@link #setScrollContainer(boolean)}. + */ + static final int SCROLL_CONTAINER = 0x00080000; + + /** + * Set by {@link #setScrollContainer(boolean)}. + */ + static final int SCROLL_CONTAINER_ADDED = 0x00100000; + + /** + * The parent this view is attached to. + * {@hide} + * + * @see #getParent() + */ + protected ViewParent mParent; + + /** + * {@hide} + */ + AttachInfo mAttachInfo; + + /** + * {@hide} + */ + @ViewDebug.ExportedProperty + 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} + */ + @ViewDebug.ExportedProperty + 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 SoftReference<Bitmap> mDrawingCache; + + /** + * 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; + private UnsetPressedState mUnsetPressedState; + + /** + * 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. + */ + @ViewDebug.ExportedProperty + 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. + */ + @ViewDebug.ExportedProperty + 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; + mViewFlags = SOUND_EFFECTS_ENABLED|HAPTIC_FEEDBACK_ENABLED; + ++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; + + boolean setScrollContainer = false; + + int x = 0; + int y = 0; + + int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; + + 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 com.android.internal.R.styleable.View_hapticFeedbackEnabled: + if (!a.getBoolean(attr, true)) { + viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED; + viewFlagMasks |= HAPTIC_FEEDBACK_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 R.styleable.View_isScrollContainer: + setScrollContainer = true; + if (a.getBoolean(attr, false)) { + setScrollContainer(true); + } + 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); + } + + if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) { + setScrollContainer(true); + } + + 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.get(mContext).getScaledFadingEdgeLength()); + } + + /** + * 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(); + } + + final ScrollabilityCache scrollabilityCache = mScrollCache; + + scrollabilityCache.scrollBarSize = a.getDimensionPixelSize( + com.android.internal.R.styleable.View_scrollbarSize, + ViewConfiguration.get(mContext).getScaledScrollBarSize()); + + Drawable track = a.getDrawable(R.styleable.View_scrollbarTrackHorizontal); + scrollabilityCache.scrollBar.setHorizontalTrackDrawable(track); + + Drawable thumb = a.getDrawable(R.styleable.View_scrollbarThumbHorizontal); + if (thumb != null) { + scrollabilityCache.scrollBar.setHorizontalThumbDrawable(thumb); + } + + boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack, + false); + if (alwaysDraw) { + scrollabilityCache.scrollBar.setAlwaysDrawHorizontalTrack(true); + } + + track = a.getDrawable(R.styleable.View_scrollbarTrackVertical); + scrollabilityCache.scrollBar.setVerticalTrackDrawable(track); + + thumb = a.getDrawable(R.styleable.View_scrollbarThumbVertical); + if (thumb != null) { + scrollabilityCache.scrollBar.setVerticalThumbDrawable(thumb); + } + + alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack, + false); + if (alwaysDraw) { + scrollabilityCache.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(ViewConfiguration.get(mContext)); + } + } + + /** + * 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(); + } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + 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. + * + * <p>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. + * + * <p>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. + * + * <p>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 != null) { + scrolled |= parent.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()); + + if (!(parent instanceof View)) { + break; + } + + 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. + * When overriding, be sure to call up through to the super class so that + * the standard focus handling will occur. + * + * @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) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (!gainFocus) { + if (isPressed()) { + setPressed(false); + } + if (imm != null && mAttachInfo != null + && mAttachInfo.mHasWindowFocus) { + imm.focusOut(this); + } + } else if (imm != null && mAttachInfo != null + && mAttachInfo.mHasWindowFocus) { + imm.focusIn(this); + } + + 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; + } + + /** + * Change whether this view is one of the set of scrollable containers in + * its window. This will be used to determine whether the window can + * resize or must pan when a soft input area is open -- scrollable + * containers allow the window to use resize mode since the container + * will appropriately shrink. + */ + public void setScrollContainer(boolean isScrollContainer) { + if (isScrollContainer) { + if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) { + mAttachInfo.mScrollContainers.add(this); + mPrivateFlags |= SCROLL_CONTAINER_ADDED; + } + mPrivateFlags |= SCROLL_CONTAINER; + } else { + if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) { + mAttachInfo.mScrollContainers.remove(this); + } + mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED); + } + } + + /** + * 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 View)) { + 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; + requestLayout(); + 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 + */ + @RemotableViewMethod + public void setVisibility(int visibility) { + setFlags(visibility, VISIBILITY_MASK); + if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false); + } + + /** + * 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. + * + * <p>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); + } + + /** + * Set whether this view should have haptic feedback for events such as + * long presses. + * + * <p>You may wish to disable haptic feedback if your view already controls + * its own haptic feedback. + * + * @param hapticFeedbackEnabled whether haptic feedback enabled for this view. + * @see #isHapticFeedbackEnabled() + * @see #performHapticFeedback(int) + * @attr ref android.R.styleable#View_hapticFeedbackEnabled + */ + public void setHapticFeedbackEnabled(boolean hapticFeedbackEnabled) { + setFlags(hapticFeedbackEnabled ? HAPTIC_FEEDBACK_ENABLED: 0, HAPTIC_FEEDBACK_ENABLED); + } + + /** + * @return whether this view should have haptic feedback enabled for events + * long presses. + * + * @see #setHapticFeedbackEnabled(boolean) + * @see #performHapticFeedback(int) + * @attr ref android.R.styleable#View_hapticFeedbackEnabled + */ + @ViewDebug.ExportedProperty + public boolean isHapticFeedbackEnabled() { + return HAPTIC_FEEDBACK_ENABLED == (mViewFlags & HAPTIC_FEEDBACK_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; + } + + /** + * This is called when a container is going to temporarily detach a child + * that currently has focus, with + * {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}. + * It will either be followed by {@link #onFinishTemporaryDetach()} or + * {@link #onDetachedFromWindow()} when the container is done. Generally + * this is currently only done ListView for a view with focus. + */ + public void onStartTemporaryDetach() { + } + + /** + * Called after {@link #onStartTemporaryDetach} when the container is done + * changing the view. + */ + public void onFinishTemporaryDetach() { + } + + /** + * capture information of this view for later analysis: developement only + * check dynamic switch to make sure we only dump view + * when ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW) is set + */ + private static void captureViewInfo(String subTag, View v) { + if (v == null || + SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_VIEW, 0) == 0) { + return; + } + ViewDebug.dumpCapturedView(subTag, v); + } + + /** + * Dispatch a key event before it is processed by any input method + * associated with the view hierarchy. This can be used to intercept + * key events in special situations before the IME consumes them; a + * typical example would be handling the BACK key to update the application's + * UI instead of allowing the IME to see it and close itself. + * + * @param event The key event to be dispatched. + * @return True if the event was handled, false otherwise. + */ + public boolean dispatchKeyEventPreIme(KeyEvent event) { + return onKeyPreIme(event.getKeyCode(), event); + } + + /** + * 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 (android.util.Config.LOGV) { + captureViewInfo("captureViewKeyEvent", this); + } + + 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) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (!hasWindowFocus) { + if (isPressed()) { + setPressed(false); + } + if (imm != null && (mPrivateFlags & FOCUSED) != 0) { + imm.focusOut(this); + } + } else if (imm != null && (mPrivateFlags & FOCUSED) != 0) { + imm.focusIn(this); + } + 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; + } + + /** + * Retrieve the overall visible display size in which the window this view is + * attached to has been positioned in. This takes into account screen + * decorations above the window, for both cases where the window itself + * is being position inside of them or the window is being placed under + * then and covered insets are used for the window to position its content + * inside. In effect, this tells you the available area where content can + * be placed and remain visible to users. + * + * <p>This function requires an IPC back to the window manager to retrieve + * the requested information, so should not be used in performance critical + * code like drawing. + * + * @param outRect Filled in with the visible display frame. If the view + * is not attached to a window, this is simply the raw display size. + */ + public void getWindowVisibleDisplayFrame(Rect outRect) { + if (mAttachInfo != null) { + try { + mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect); + } catch (RemoteException e) { + return; + } + // XXX This is really broken, and probably all needs to be done + // in the window manager, and we need to know more about whether + // we want the area behind or in front of the IME. + final Rect insets = mAttachInfo.mVisibleInsets; + outRect.left += insets.left; + outRect.top += insets.top; + outRect.right -= insets.right; + outRect.bottom -= insets.bottom; + return; + } + Display d = WindowManagerImpl.getDefault().getDefaultDisplay(); + outRect.set(0, 0, d.getWidth(), d.getHeight()); + } + + /** + * Private function to aggregate all per-view attributes in to the view + * root. + */ + void dispatchCollectViewAttributes(int visibility) { + performCollectViewAttributes(visibility); + } + + void performCollectViewAttributes(int visibility) { + //noinspection PointlessBitwiseExpression + 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. + */ + @ViewDebug.CapturedViewProperty + public final Context getContext() { + return mContext; + } + + /** + * Handle a key event before it is processed by any input method + * associated with the view hierarchy. This can be used to intercept + * key events in special situations before the IME consumes them; a + * typical example would be handling the BACK key to update the application's + * UI instead of allowing the IME to see it and close itself. + * + * @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 onKeyPreIme(int keyCode, KeyEvent event) { + return false; + } + + /** + * 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; + } + + /** + * Check whether the called view is a text editor, in which case it + * would make sense to automatically display a soft input window for + * it. Subclasses should override this if they implement + * {@link #onCreateInputConnection(EditorInfo)} to return true if + * a call on that method would return a non-null InputConnection. The + * default implementation always returns false. + * + * @return Returns true if this view is a text editor, else false. + */ + public boolean onCheckIsTextEditor() { + return false; + } + + /** + * Create a new InputConnection for an InputMethod to interact + * with the view. The default implementation returns null, since it doesn't + * support input methods. You can override this to implement such support. + * This is only needed for views that take focus and text input. + * + * <p>When implementing this, you probably also want to implement + * {@link #onCheckIsTextEditor()} to indicate you will return a + * non-null InputConnection. + * + * @param outAttrs Fill in with attribute information about the connection. + */ + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return null; + } + + /** + * 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(); + } + } + + if (mUnsetPressedState == null) { + mUnsetPressedState = new UnsetPressedState(); + } + + if (!post(mUnsetPressedState)) { + // If the post failed, unpress right now + mUnsetPressedState.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.get(mContext).getScaledTouchSlop(); + 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(); + } + if (mAttachInfo != null) { + mAttachInfo.mViewVisibilityChanged = true; + } + } + + /* 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 (mAttachInfo != null) { + mAttachInfo.mViewVisibilityChanged = true; + } + } + + if ((changed & WILL_NOT_CACHE_DRAWING) != 0) { + destroyDrawingCache(); + } + + if ((changed & DRAWING_CACHE_ENABLED) != 0) { + destroyDrawingCache(); + mPrivateFlags &= ~DRAWING_CACHE_VALID; + } + + if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) { + destroyDrawingCache(); + 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; + + final AttachInfo ai = mAttachInfo; + if (ai != null) { + ai.mViewScrollChanged = 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. + */ + @ViewDebug.CapturedViewProperty + public final int getTop() { + return mTop; + } + + /** + * Bottom position of this view relative to its parent. + * + * @return The bottom of this view, in pixels. + */ + @ViewDebug.CapturedViewProperty + public final int getBottom() { + return mBottom; + } + + /** + * Left position of this view relative to its parent. + * + * @return The left edge of this view, in pixels. + */ + @ViewDebug.CapturedViewProperty + public final int getLeft() { + return mLeft; + } + + /** + * Right position of this view relative to its parent. + * + * @return The right edge of this view, in pixels. + */ + @ViewDebug.CapturedViewProperty + 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; + final ViewParent p = mParent; + final AttachInfo ai = mAttachInfo; + if (p != null && ai != null) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; + final Rect r = ai.mTmpInvalRect; + r.set(dirty.left - scrollX, dirty.top - scrollY, + dirty.right - scrollX, dirty.bottom - scrollY); + mParent.invalidateChild(this, r); + } + } + } + + /** + * 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; + final ViewParent p = mParent; + final AttachInfo ai = mAttachInfo; + if (p != null && ai != null && l < r && t < b) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; + final Rect tmpr = ai.mTmpInvalRect; + tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY); + p.invalidateChild(this, tmpr); + } + } + } + + /** + * 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; + final ViewParent p = mParent; + final AttachInfo ai = mAttachInfo; + if (p != null && ai != null) { + final Rect r = ai.mTmpInvalRect; + r.set(0, 0, mRight - mLeft, mBottom - mTop); + // Don't call invalidate -- we don't want to internally scroll + // our own bounds + p.invalidateChild(this, r); + } + } + } + + /** + * @return A handler associated with the thread running the View. This + * handler can be used to pump events in the UI events queue. + */ + public 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 { + // Assume that post will succeed later + ViewRoot.getRunQueue().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 { + // Assume that post will succeed later + ViewRoot.getRunQueue().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 { + // Assume that post will succeed later + ViewRoot.getRunQueue().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() { + postInvalidateDelayed(0); + } + + /** + * 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) { + postInvalidateDelayed(0, left, top, right, bottom); + } + + /** + * 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) { + final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire(); + info.target = this; + info.left = left; + info.top = top; + info.right = right; + info.bottom = bottom; + + final Message msg = Message.obtain(); + msg.what = AttachInfo.INVALIDATE_RECT_MSG; + msg.obj = info; + 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 + && !isVerticalScrollBarHidden(); + + 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); + } + } + } + } + + /** + * Override this if the vertical scrollbar needs to be hidden in a subclass, like when + * FastScroller is visible. + * @return whether to temporarily hide the vertical scrollbar + * @hide + */ + protected boolean isVerticalScrollBarHidden() { + return false; + } + + /** + * <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); + } + destroyDrawingCache(); + } + + /** + * @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; + } + if ((mPrivateFlags&SCROLL_CONTAINER) != 0) { + mAttachInfo.mScrollContainers.add(this); + mPrivateFlags |= SCROLL_CONTAINER_ADDED; + } + 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(); + if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) { + mAttachInfo.mScrollContainers.remove(this); + mPrivateFlags &= ~SCROLL_CONTAINER_ADDED; + } + 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) { + buildDrawingCache(); + } + return mDrawingCache == null ? null : mDrawingCache.get(); + } + + /** + * <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) { + final Bitmap bitmap = mDrawingCache.get(); + if (bitmap != null) bitmap.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 || + mDrawingCache.get() == 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.get(mContext).getScaledMaximumDrawingCacheSize())) { + destroyDrawingCache(); + return; + } + + boolean clear = true; + Bitmap bitmap = mDrawingCache == null ? null : mDrawingCache.get(); + + 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; + } + + // Try to cleanup memory + if (bitmap != null) bitmap.recycle(); + + try { + bitmap = Bitmap.createBitmap(width, height, quality); + mDrawingCache = new SoftReference<Bitmap>(bitmap); + } catch (OutOfMemoryError e) { + // If there is not enough memory to create the bitmap cache, just + // ignore the issue as bitmap caches are not required to draw the + // view hierarchy + mDrawingCache = null; + return; + } + + 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); + + mPrivateFlags |= DRAWN; + + // Fast path for layouts with no backgrounds + if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + 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; + } + } + + /** + * Indicates whether this View is currently in edit mode. A View is usually + * in edit mode when displayed within a developer tool. For instance, if + * this View is being drawn by a visual user interface builder, this method + * should return true. + * + * Subclasses should check the return value of this method to provide + * different behaviors if their normal behavior might interfere with the + * host environment. For instance: the class spawns a thread in its + * constructor, the drawing code relies on device-specific features, etc. + * + * This method is usually checked in the drawing code of custom widgets. + * + * @return True if this View is in edit mode, false otherwise. + */ + public boolean isInEditMode() { + return false; + } + + /** + * If the View draws content inside its padding and enables fading edges, + * it needs to support padding offsets. Padding offsets are added to the + * fading edges to extend the length of the fade so that it covers pixels + * drawn inside the padding. + * + * Subclasses of this class should override this method if they need + * to draw content inside the padding. + * + * @return True if padding offset must be applied, false otherwise. + * + * @see #getLeftPaddingOffset() + * @see #getRightPaddingOffset() + * @see #getTopPaddingOffset() + * @see #getBottomPaddingOffset() + * + * @since CURRENT + */ + protected boolean isPaddingOffsetRequired() { + return false; + } + + /** + * Amount by which to extend the left fading region. Called only when + * {@link #isPaddingOffsetRequired()} returns true. + * + * @return The left padding offset in pixels. + * + * @see #isPaddingOffsetRequired() + * + * @since CURRENT + */ + protected int getLeftPaddingOffset() { + return 0; + } + + /** + * Amount by which to extend the right fading region. Called only when + * {@link #isPaddingOffsetRequired()} returns true. + * + * @return The right padding offset in pixels. + * + * @see #isPaddingOffsetRequired() + * + * @since CURRENT + */ + protected int getRightPaddingOffset() { + return 0; + } + + /** + * Amount by which to extend the top fading region. Called only when + * {@link #isPaddingOffsetRequired()} returns true. + * + * @return The top padding offset in pixels. + * + * @see #isPaddingOffsetRequired() + * + * @since CURRENT + */ + protected int getTopPaddingOffset() { + return 0; + } + + /** + * Amount by which to extend the bottom fading region. Called only when + * {@link #isPaddingOffsetRequired()} returns true. + * + * @return The bottom padding offset in pixels. + * + * @see #isPaddingOffsetRequired() + * + * @since CURRENT + */ + protected int getBottomPaddingOffset() { + return 0; + } + + /** + * 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); + } + + mPrivateFlags |= DRAWN; + + /* + * 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 + 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 + int paddingLeft = mPaddingLeft; + int paddingTop = mPaddingTop; + + final boolean offsetRequired = isPaddingOffsetRequired(); + if (offsetRequired) { + paddingLeft += getLeftPaddingOffset(); + paddingTop += getTopPaddingOffset(); + } + + int left = mScrollX + paddingLeft; + int right = left + mRight - mLeft - mPaddingRight - paddingLeft; + int top = mScrollY + paddingTop; + int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop; + + if (offsetRequired) { + right += getRightPaddingOffset(); + bottom += getBottomPaddingOffset(); + } + + 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 + 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; + + int viewStateIndex = (((privateFlags & PRESSED) != 0) ? 1 : 0); + + viewStateIndex = (viewStateIndex << 1) + + (((mViewFlags & ENABLED_MASK) == ENABLED) ? 1 : 0); + + viewStateIndex = (viewStateIndex << 1) + (isFocused() ? 1 : 0); + + viewStateIndex = (viewStateIndex << 1) + + (((privateFlags & SELECTED) != 0) ? 1 : 0); + + final 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=" + ((privateFlags & PRESSED) != 0) + + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED) + + " fo=" + hasFocus() + + " sl=" + ((privateFlags & SELECTED) != 0) + + " 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) { + Rect padding = sThreadLocal.get(); + if (padding == null) { + padding = new Rect(); + sThreadLocal.set(padding); + } + 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() { + if (mAttachInfo != null) { + final View v = mAttachInfo.mRootView; + if (v != null) { + return v; + } + } + + 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; + + ViewParent viewParent = mParent; + while (viewParent instanceof View) { + final View view = (View)viewParent; + location[0] += view.mLeft - view.mScrollX; + location[1] += view.mTop - view.mScrollY; + viewParent = view.mParent; + } + + if (viewParent instanceof ViewRoot) { + // *cough* + final ViewRoot vr = (ViewRoot)viewParent; + location[1] -= vr.mCurScrollY; + } + } + + /** + * {@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 + */ + @ViewDebug.CapturedViewProperty + 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) { + final AttachInfo attachInfo = mAttachInfo; + if (region != null && attachInfo != 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. + final int[] location = attachInfo.mTransparentLocation; + getLocationInWindow(location); + region.op(location[0], location[1], location[0] + mRight - mLeft, + location[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. + * + * <p>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. + * + * <p>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} + */ + public void playSoundEffect(int soundConstant) { + if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) { + return; + } + mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant); + } + + /** + * Provide haptic feedback to the user for this view. + * + * <p>The framework will provide haptic feedback for some built in actions, + * such as long presses, but you may wish to provide feedback for your + * own widget. + * + * <p>The feedback will only be performed if + * {@link #isHapticFeedbackEnabled()} is true. + * + * @param feedbackConstant One of the constants defined in + * {@link HapticFeedbackConstants} + */ + public boolean performHapticFeedback(int feedbackConstant) { + return performHapticFeedback(feedbackConstant, 0); + } + + /** + * Like {@link #performHapticFeedback(int)}, with additional options. + * + * @param feedbackConstant One of the constants defined in + * {@link HapticFeedbackConstants} + * @param flags Additional flags as per {@link HapticFeedbackConstants}. + */ + public boolean performHapticFeedback(int feedbackConstant, int flags) { + if (mAttachInfo == null) { + return false; + } + if ((flags&HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0 + && !isHapticFeedbackEnabled()) { + return false; + } + return mAttachInfo.mRootCallbacks.performHapticFeedback( + feedbackConstant, + (flags&HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0); + } + + /** + * 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(); + final AttachInfo attachInfo = mAttachInfo; + if (r != null && attachInfo != 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); + } + final int[] location = attachInfo.mTransparentLocation; + getLocationInWindow(location); + r.translate(location[0], location[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 android.view.View#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 Callbacks { + void playSoundEffect(int effectId); + boolean performHapticFeedback(int effectId, boolean always); + } + + /** + * InvalidateInfo is used to post invalidate(int, int, int, int) messages + * to a Handler. This class contains the target (View) to invalidate and + * the coordinates of the dirty rectangle. + * + * For performance purposes, this class also implements a pool of up to + * POOL_LIMIT objects that get reused. This reduces memory allocations + * whenever possible. + * + * The pool is implemented as a linked list of InvalidateInfo object with + * the root pointing to the next available InvalidateInfo. If the root + * is null (i.e. when all instances from the pool have been acquired), + * then a new InvalidateInfo is created and returned to the caller. + * + * An InvalidateInfo is sent back to the pool by calling its release() + * method. If the pool is full the object is simply discarded. + * + * This implementation follows the object pool pattern used in the + * MotionEvent class. + */ + static class InvalidateInfo { + private static final int POOL_LIMIT = 10; + private static final Object sLock = new Object(); + + private static int sAcquiredCount = 0; + private static InvalidateInfo sRoot; + + private InvalidateInfo next; + + View target; + + int left; + int top; + int right; + int bottom; + + static InvalidateInfo acquire() { + synchronized (sLock) { + if (sRoot == null) { + return new InvalidateInfo(); + } + + InvalidateInfo info = sRoot; + sRoot = info.next; + sAcquiredCount--; + + return info; + } + } + + void release() { + synchronized (sLock) { + if (sAcquiredCount < POOL_LIMIT) { + sAcquiredCount++; + next = sRoot; + sRoot = this; + } + } + } + } + + final IWindowSession mSession; + + final IWindow mWindow; + + final IBinder mWindowToken; + + final Callbacks mRootCallbacks; + + /** + * The top view of the hierarchy. + */ + View mRootView; + + IBinder mPanelParentWindowToken; + Surface mSurface; + + /** + * Left position of this view's window + */ + int mWindowLeft; + + /** + * Top position of this view's window + */ + int mWindowTop; + + /** + * For windows that are full-screen but using insets to layout inside + * of the screen decorations, these are the current insets for the + * content of the window. + */ + final Rect mContentInsets = new Rect(); + + /** + * For windows that are full-screen but using insets to layout inside + * of the screen decorations, these are the current insets for the + * actual visible parts of the window. + */ + final Rect mVisibleInsets = new Rect(); + + /** + * The internal insets given by this window. This value is + * supplied by the client (through + * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will + * be given to the window manager when changed to be used in laying + * out windows behind it. + */ + final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets + = new ViewTreeObserver.InternalInsetsInfo(); + + /** + * All views in the window's hierarchy that serve as scroll containers, + * used to determine if the window can be resized or must be panned + * to adjust for a soft input area. + */ + final ArrayList<View> mScrollContainers = new ArrayList<View>(); + + /** + * 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; + + /** + * Set if the visibility of any views has changed. + */ + boolean mViewVisibilityChanged; + + /** + * Set to true if a view has been scrolled. + */ + boolean mViewScrollChanged; + + /** + * Global to the view hierarchy used as a temporary for dealing with + * x/y points in the transparent region computations. + */ + final int[] mTransparentLocation = new int[2]; + + /** + * Global to the view hierarchy used as a temporary for dealing with + * x/y points in the ViewGroup.invalidateChild implementation. + */ + final int[] mInvalidateChildLocation = new int[2]; + + /** + * 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; + + /** + * Temporary for use in computing invalidate rectangles while + * calling up the hierarchy. + */ + final Rect mTmpInvalRect = new Rect(); + + /** + * Creates a new set of attachment information with the specified + * events handler and thread. + * + * @param handler the events handler the view must use + */ + AttachInfo(IWindowSession session, IWindow window, + Handler handler, Callbacks effectPlayer) { + mSession = session; + mWindow = window; + mWindowToken = window.asBinder(); + mHandler = handler; + mRootCallbacks = 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; + + public int scrollBarSize; + public ScrollBarDrawable scrollBar; + + public final Paint paint; + public final Matrix matrix; + public Shader shader; + + private int mLastColor; + + public ScrollabilityCache(ViewConfiguration configuration) { + fadingEdgeLength = configuration.getScaledFadingEdgeLength(); + scrollBarSize = configuration.getScaledScrollBarSize(); + + 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); + // Restore the default transfer mode (src_over) + paint.setXfermode(null); + } + } + } +} diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java new file mode 100644 index 0000000..d3f48c6 --- /dev/null +++ b/core/java/android/view/ViewConfiguration.java @@ -0,0 +1,424 @@ +/* + * 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.util.DisplayMetrics; +import android.util.SparseArray; + +/** + * 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 = 10; + + /** + * 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 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 = 500; + + /** + * Defines the duration in milliseconds we will wait to see if a touch event + * is a tap 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 between the first tap's up event and + * the second tap's down event for an interaction to be considered a + * double-tap. + */ + private static final int DOUBLE_TAP_TIMEOUT = 300; + + /** + * 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; + + /** + * 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 = 25; + + /** + * Distance between the first touch and second touch to still be considered a double tap + */ + private static final int DOUBLE_TAP_SLOP = 100; + + /** + * 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. + */ + @Deprecated + private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // HVGA screen, ARGB8888 + + /** + * The coefficient of friction applied to flings/scrolls. + */ + private static float SCROLL_FRICTION = 0.015f; + + private final int mEdgeSlop; + private final int mFadingEdgeLength; + private final int mMinimumFlingVelocity; + private final int mScrollbarSize; + private final int mTouchSlop; + private final int mDoubleTapSlop; + private final int mWindowTouchSlop; + private final int mMaximumDrawingCacheSize; + + private static final SparseArray<ViewConfiguration> sConfigurations = + new SparseArray<ViewConfiguration>(2); + + /** + * @deprecated Use {@link android.view.ViewConfiguration#get(android.content.Context)} instead. + */ + @Deprecated + public ViewConfiguration() { + mEdgeSlop = EDGE_SLOP; + mFadingEdgeLength = FADING_EDGE_LENGTH; + mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY; + mScrollbarSize = SCROLL_BAR_SIZE; + mTouchSlop = TOUCH_SLOP; + mDoubleTapSlop = DOUBLE_TAP_SLOP; + mWindowTouchSlop = WINDOW_TOUCH_SLOP; + //noinspection deprecation + mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE; + } + + /** + * Creates a new configuration for the specified context. The configuration depends on + * various parameters of the context, like the dimension of the display or the density + * of the display. + * + * @param context The application context used to initialize this view configuration. + * + * @see #get(android.content.Context) + * @see android.util.DisplayMetrics + */ + private ViewConfiguration(Context context) { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final float density = metrics.density; + + mEdgeSlop = (int) (density * EDGE_SLOP + 0.5f); + mFadingEdgeLength = (int) (density * FADING_EDGE_LENGTH + 0.5f); + mMinimumFlingVelocity = (int) (density * MINIMUM_FLING_VELOCITY + 0.5f); + mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f); + mTouchSlop = (int) (density * TOUCH_SLOP + 0.5f); + mDoubleTapSlop = (int) (density * DOUBLE_TAP_SLOP + 0.5f); + mWindowTouchSlop = (int) (density * WINDOW_TOUCH_SLOP + 0.5f); + + // Size of the screen in bytes, in ARGB_8888 format + mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels; + } + + /** + * Returns a configuration for the specified context. The configuration depends on + * various parameters of the context, like the dimension of the display or the + * density of the display. + * + * @param context The application context used to initialize the view configuration. + */ + public static ViewConfiguration get(Context context) { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final int density = (int) (100.0f * metrics.density); + + ViewConfiguration configuration = sConfigurations.get(density); + if (configuration == null) { + configuration = new ViewConfiguration(context); + sConfigurations.put(density, configuration); + } + + return configuration; + } + + /** + * @return The width of the horizontal scrollbar and the height of the vertical + * scrollbar in pixels + * + * @deprecated Use {@link #getScaledScrollBarSize()} instead. + */ + @Deprecated + public static int getScrollBarSize() { + return SCROLL_BAR_SIZE; + } + + /** + * @return The width of the horizontal scrollbar and the height of the vertical + * scrollbar in pixels + */ + public int getScaledScrollBarSize() { + return mScrollbarSize; + } + + /** + * @return the length of the fading edges in pixels + * + * @deprecated Use {@link #getScaledFadingEdgeLength()} instead. + */ + @Deprecated + public static int getFadingEdgeLength() { + return FADING_EDGE_LENGTH; + } + + /** + * @return the length of the fading edges in pixels + */ + public int getScaledFadingEdgeLength() { + return mFadingEdgeLength; + } + + /** + * @return the duration in milliseconds of the pressed state in child + * components. + */ + public static int getPressedStateDuration() { + return PRESSED_STATE_DURATION; + } + + /** + * @return the duration in milliseconds before a press turns into + * a long press + */ + public static int getLongPressTimeout() { + return LONG_PRESS_TIMEOUT; + } + + /** + * @return the duration in milliseconds we will wait to see if a touch event + * is a tap 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 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 the duration in milliseconds between the first tap's up event and + * the second tap's down event for an interaction to be considered a + * double-tap. + * @hide pending API council + */ + public static int getDoubleTapTimeout() { + return DOUBLE_TAP_TIMEOUT; + } + + /** + * @return Inset in pixels to look for touchable content when the user touches the edge of the + * screen + * + * @deprecated Use {@link #getScaledEdgeSlop()} instead. + */ + @Deprecated + public static int getEdgeSlop() { + return EDGE_SLOP; + } + + /** + * @return Inset in pixels to look for touchable content when the user touches the edge of the + * screen + */ + public int getScaledEdgeSlop() { + return mEdgeSlop; + } + + /** + * @return Distance a touch can wander before we think the user is scrolling in pixels + * + * @deprecated Use {@link #getScaledTouchSlop()} instead. + */ + @Deprecated + public static int getTouchSlop() { + return TOUCH_SLOP; + } + + /** + * @return Distance a touch can wander before we think the user is scrolling in pixels + */ + public int getScaledTouchSlop() { + return mTouchSlop; + } + + /** + * @return Distance between the first touch and second touch to still be + * considered a double tap + * @deprecated Use {@link #getScaledDoubleTapSlop()} instead. + * @hide The only client of this should be GestureDetector, which needs this + * for clients that still use its deprecated constructor. + */ + @Deprecated + public static int getDoubleTapSlop() { + return DOUBLE_TAP_SLOP; + } + + /** + * @return Distance between the first touch and second touch to still be + * considered a double tap + * @hide pending API council + */ + public int getScaledDoubleTapSlop() { + return mDoubleTapSlop; + } + + /** + * @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. + * + * @deprecated Use {@link #getScaledWindowTouchSlop()} instead. + */ + @Deprecated + public static int getWindowTouchSlop() { + return WINDOW_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 int getScaledWindowTouchSlop() { + return mWindowTouchSlop; + } + + /** + * @return Minimum velocity to initiate a fling, as measured in pixels per second. + * + * @deprecated Use {@link #getScaledMinimumFlingVelocity()} instead. + */ + @Deprecated + public static int getMinimumFlingVelocity() { + return MINIMUM_FLING_VELOCITY; + } + + /** + * @return Minimum velocity to initiate a fling, as measured in pixels per second. + */ + public int getScaledMinimumFlingVelocity() { + return mMinimumFlingVelocity; + } + + /** + * The maximum drawing cache size expressed in bytes. + * + * @return the maximum size of View's drawing cache expressed in bytes + * + * @deprecated Use {@link #getScaledMaximumDrawingCacheSize()} instead. + */ + @Deprecated + public static int getMaximumDrawingCacheSize() { + //noinspection deprecation + return MAXIMUM_DRAWING_CACHE_SIZE; + } + + /** + * The maximum drawing cache size expressed in bytes. + * + * @return the maximum size of View's drawing cache expressed in bytes + */ + public int getScaledMaximumDrawingCacheSize() { + return mMaximumDrawingCacheSize; + } + + /** + * 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..883c7bd --- /dev/null +++ b/core/java/android/view/ViewDebug.java @@ -0,0 +1,1128 @@ +/* + * 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 android.os.Environment; + +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; + + /** + * The system property of dynamic switch for capturing view information + * when it is set, we dump interested fields and methods for the view on focus + */ + static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview"; + + /** + * The system property of dynamic switch for capturing event information + * when it is set, we log key events, touch/motion and trackball events + */ + static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent"; + + /** + * 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(); + } + + /** + * This annotation can be used to mark fields and methods to be dumped when + * the view is captured. Methods with this annotation must have no arguments + * and must return <some type of data>. + * + * @hide pending API Council approval + */ + @Target({ ElementType.FIELD, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface CapturedViewProperty { + /** + * When retrieveReturn is true, we need to retrieve second level methods + * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod() + * we will set retrieveReturn = true on the annotation of + * myView.getFirstLevelMethod() + * @return true if we need the second level methods + */ + boolean retrieveReturn() default false; + } + + private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null; + private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null; + + // 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>/EXTERNAL/view-recycler/PREFIX.traces</code> and + * <code>/EXTERNAL/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>/EXTERNAL/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>/EXTERNAL/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(Environment.getExternalStorageDirectory(), "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(Environment.getExternalStorageDirectory(), "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>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and + * <code>/EXTERNAL/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>/EXTERNAL/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(Environment.getExternalStorageDirectory(), "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>/EXTERNAL/view-hierarchy/PREFIX.traces</code>. + * + * Calling this method creates the file <code>/EXTERNAL/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(Environment.getExternalStorageDirectory(), "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) { + try { + methodValue = resources.getResourceTypeName(id) + '/' + + resources.getResourceEntryName(id); + } catch (Resources.NotFoundException e) { + methodValue = "UNKNOWN"; + } + } 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) { + try { + fieldValue = resources.getResourceTypeName(id) + '/' + + resources.getResourceEntryName(id); + } catch (Resources.NotFoundException e) { + fieldValue = "UNKNOWN"; + } + } 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; + } + + private static Field[] capturedViewGetPropertyFields(Class<?> klass) { + if (mCapturedViewFieldsForClasses == null) { + mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>(); + } + final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses; + + Field[] fields = map.get(klass); + if (fields != null) { + return fields; + } + + final ArrayList<Field> foundFields = new ArrayList<Field>(); + fields = klass.getFields(); + + int count = fields.length; + for (int i = 0; i < count; i++) { + final Field field = fields[i]; + if (field.isAnnotationPresent(CapturedViewProperty.class)) { + field.setAccessible(true); + foundFields.add(field); + } + } + + fields = foundFields.toArray(new Field[foundFields.size()]); + map.put(klass, fields); + + return fields; + } + + private static Method[] capturedViewGetPropertyMethods(Class<?> klass) { + if (mCapturedViewMethodsForClasses == null) { + mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>(); + } + final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses; + + Method[] methods = map.get(klass); + if (methods != null) { + return methods; + } + + final ArrayList<Method> foundMethods = new ArrayList<Method>(); + methods = klass.getMethods(); + + int count = methods.length; + for (int i = 0; i < count; i++) { + final Method method = methods[i]; + if (method.getParameterTypes().length == 0 && + method.isAnnotationPresent(CapturedViewProperty.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 String capturedViewExportMethods(Object obj, Class<?> klass, + String prefix) { + + if (obj == null) { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + final Method[] methods = capturedViewGetPropertyMethods(klass); + + int count = methods.length; + for (int i = 0; i < count; i++) { + final Method method = methods[i]; + try { + Object methodValue = method.invoke(obj, (Object[]) null); + final Class<?> returnType = method.getReturnType(); + + CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class); + if (property.retrieveReturn()) { + //we are interested in the second level data only + sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#")); + } else { + sb.append(prefix); + sb.append(method.getName()); + sb.append("()="); + + if (methodValue != null) { + final String value = methodValue.toString().replace("\n", "\\n"); + sb.append(value); + } else { + sb.append("null"); + } + sb.append("; "); + } + } catch (IllegalAccessException e) { + //Exception IllegalAccess, it is OK here + //we simply ignore this method + } catch (InvocationTargetException e) { + //Exception InvocationTarget, it is OK here + //we simply ignore this method + } + } + return sb.toString(); + } + + private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) { + + if (obj == null) { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + final Field[] fields = capturedViewGetPropertyFields(klass); + + int count = fields.length; + for (int i = 0; i < count; i++) { + final Field field = fields[i]; + try { + Object fieldValue = field.get(obj); + + sb.append(prefix); + sb.append(field.getName()); + sb.append("="); + + if (fieldValue != null) { + final String value = fieldValue.toString().replace("\n", "\\n"); + sb.append(value); + } else { + sb.append("null"); + } + sb.append(' '); + } catch (IllegalAccessException e) { + //Exception IllegalAccess, it is OK here + //we simply ignore this field + } + } + return sb.toString(); + } + + /** + * dump view info for id based instrument test generation + * (and possibly further data analysis). The results are dumped + * to the log. + * @param tag for log + * @param view for dump + * + * @hide pending API Council approval + */ + public static void dumpCapturedView(String tag, Object view) { + Class<?> klass = view.getClass(); + StringBuilder sb = new StringBuilder(klass.getName() + ": "); + sb.append(capturedViewExportFields(view, klass, "")); + sb.append(capturedViewExportMethods(view, klass, "")); + Log.d(tag, sb.toString()); + } +} diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java new file mode 100644 index 0000000..70cc2a9 --- /dev/null +++ b/core/java/android/view/ViewGroup.java @@ -0,0 +1,3478 @@ +/* + * 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.graphics.RectF; +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.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 Transformation mChildTransformation; + private RectF mInvalidateRegion; + + // 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}. + * + * {@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; + } + + /** + * {@inheritDoc} + */ + 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 dispatchKeyEventPreIme(KeyEvent event) { + if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + return super.dispatchKeyEventPreIme(event); + } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + return mFocused.dispatchKeyEventPreIme(event); + } + return false; + } + + /** + * {@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 dispatchKeyShortcutEvent(KeyEvent event) { + if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { + return super.dispatchKeyShortcutEvent(event); + } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { + return mFocused.dispatchKeyShortcutEvent(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() { + // If we still have a motion target, we are still in the process of + // dispatching motion events to a child; we need to get rid of that + // child to avoid dispatching events to it after the window is torn + // down. To make sure we keep the child in a consistent state, we + // first send it an ACTION_CANCEL motion event. + if (mMotionTarget != null) { + final long now = SystemClock.uptimeMillis(); + final MotionEvent event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); + mMotionTarget.dispatchTouchEvent(event); + event.recycle(); + mMotionTarget = null; + } + + 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); + + } + + // We will draw our child's animation, let's reset the flag + mPrivateFlags &= ~DRAW_ANIMATION; + 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) { + if (mChildTransformation != null) { + mChildTransformation.clear(); + } + mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION; + } + + Transformation transformToApply = null; + final Animation a = child.getAnimation(); + boolean concatMatrix = false; + + final int childWidth = cr - cl; + final int childHeight = cb - ct; + + if (a != null) { + if (mInvalidateRegion == null) { + mInvalidateRegion = new RectF(); + } + final RectF region = mInvalidateRegion; + + final boolean initialized = a.isInitialized(); + if (!initialized) { + a.initialize(childWidth, childHeight, getWidth(), getHeight()); + a.initializeInvalidateRegion(0, 0, childWidth, childHeight); + child.onAnimationStart(); + } + + if (mChildTransformation == null) { + mChildTransformation = new Transformation(); + } + 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) { + // The child need to draw an animation, potentially offscreen, so + // make sure we do not cancel invalidate requests + mPrivateFlags |= DRAW_ANIMATION; + invalidate(cl, ct, cr, cb); + } + } else { + a.getInvalidateRegion(0, 0, childWidth, childHeight, region, transformToApply); + + // The child need to draw an animation, potentially offscreen, so + // make sure we do not cancel invalidate requests + mPrivateFlags |= DRAW_ANIMATION; + // Enlarge the invalidate region to account for rounding errors + // in Animation#getInvalidateRegion(); Using 0.5f is unfortunately + // not enough for some types of animations (e.g. scale down.) + final int left = cl + (int) (region.left - 1.0f); + final int top = ct + (int) (region.top - 1.0f); + invalidate(left, top, + left + (int) (region.width() + 1.0f), + top + (int) (region.height() + 1.0f)); + } + } + } else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) == + FLAG_SUPPORT_STATIC_TRANSFORMATIONS) { + if (mChildTransformation == null) { + mChildTransformation = new Transformation(); + } + 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) && + (child.mPrivateFlags & DRAW_ANIMATION) == 0) { + 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 + childWidth, sy + childHeight); + } else { + canvas.clipRect(0, 0, childWidth, childHeight); + } + } + + // Clear the flag as early as possible to allow draw() implementations + // to call invalidate() successfully when doing animations + child.mPrivateFlags |= DRAWN; + + if (hasNoCache) { + // Fast path for layouts with no backgrounds + if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + 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; + } + 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); + } + } + + /** + * When this property is set to true, 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 property to true. + * + * @param enabled True to enable static transformations on children, false otherwise. + * + * @see #FLAG_SUPPORT_STATIC_TRANSFORMATIONS + */ + protected void setStaticTransformationsEnabled(boolean enabled) { + setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled); + } + + /** + * {@inheritDoc} + * + * @see #setStaticTransformationsEnabled(boolean) + */ + 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 (view.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 (child.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 the 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)}. + * + * @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 AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + final int[] location = attachInfo.mInvalidateChildLocation; + location[CHILD_LEFT_INDEX] = child.mLeft; + location[CHILD_TOP_INDEX] = child.mTop; + + // If the child is drawing an animation, we want to copy this flag onto + // ourselves and the parent to make sure the invalidate request goes + // through + final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION; + + do { + if (drawAnimation && parent instanceof View) { + ((View) parent).mPrivateFlags |= DRAW_ANIMATION; + } + 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 & DRAW_ANIMATION) == DRAW_ANIMATION) { + 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 (view.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..b456c5d --- /dev/null +++ b/core/java/android/view/ViewParent.java @@ -0,0 +1,211 @@ +/* + * 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); + + /** + * 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); +} diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java new file mode 100644 index 0000000..9b13d38 --- /dev/null +++ b/core/java/android/view/ViewRoot.java @@ -0,0 +1,2872 @@ +/* + * 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.IInputMethodCallback; +import com.android.internal.view.IInputMethodSession; + +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.*; +import android.os.Process; +import android.os.SystemProperties; +import android.util.AndroidRuntimeException; +import android.util.Config; +import android.util.Log; +import android.util.EventLog; +import android.util.SparseArray; +import android.util.DisplayMetrics; +import android.view.View.MeasureSpec; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.Scroller; +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"}) +public final class ViewRoot extends Handler implements ViewParent, + View.AttachInfo.Callbacks { + 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_LAYOUT = false || LOCAL_LOGV; + private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV; + private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV; + private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV; + private static final boolean DEBUG_IMF = false || 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; + + static long sInstanceCount = 0; + + static IWindowSession sWindowSession; + + static final Object mStaticInit = new Object(); + static boolean mInitialized = false; + + static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); + + long mLastTrackballTime = 0; + final TrackballAxis mTrackballAxisX = new TrackballAxis(); + final TrackballAxis mTrackballAxisY = new TrackballAxis(); + + final int[] mTmpLocation = new int[2]; + + final InputMethodCallback mInputMethodCallback; + final SparseArray<Object> mPendingEvents = new SparseArray<Object>(); + int mPendingEventSeq = 0; + + final Thread mThread; + + final WindowLeaked mLocation; + + final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); + + final W mWindow; + + View mView; + View mFocusedView; + View mRealFocusedView; // this is not set to null in touch mode + int mViewVisibility; + boolean mAppVisible = true; + + final Region mTransparentRegion; + final Region mPreviousTransparentRegion; + + int mWidth; + int mHeight; + Rect mDirty; // will be a graphics.Region soon + + final View.AttachInfo mAttachInfo; + + final Rect mTempRect; // used in the transaction to not thrash the heap. + final Rect mVisRect; // used to retrieve visible rect of focused view. + final Point mVisPoint; // used to retrieve global offset of focused view. + + boolean mTraversalScheduled; + boolean mWillDrawSoon; + boolean mLayoutRequested; + boolean mFirst; + boolean mReportNextDraw; + boolean mFullRedrawNeeded; + boolean mNewSurfaceNeeded; + boolean mHasHadWindowFocus; + boolean mLastWasImTarget; + + boolean mWindowAttributesChanged = false; + + // These can be accessed by any thread, must be protected with a lock. + Surface mSurface; + + boolean mAdded; + boolean mAddedTouchMode; + + /*package*/ int mAddNesting; + + // These are accessed by multiple threads. + final Rect mWinFrame; // frame given by window manager. + + final Rect mPendingVisibleInsets = new Rect(); + final Rect mPendingContentInsets = new Rect(); + final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets + = new ViewTreeObserver.InternalInsetsInfo(); + + boolean mScrollMayChange; + int mSoftInputMode; + View mLastScrolledFocus; + int mScrollY; + int mCurScrollY; + Scroller mScroller; + + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLContext mEglContext; + EGLSurface mEglSurface; + GL11 mGL; + Canvas mGlCanvas; + boolean mUseGL; + boolean mGlWanted; + + final ViewConfiguration mViewConfiguration; + + /** + * see {@link #playSoundEffect(int)} + */ + AudioManager mAudioManager; + + private final float mDensity; + + public ViewRoot(Context context) { + 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 { + InputMethodManager imm = InputMethodManager.getInstance(context); + sWindowSession = IWindowManager.Stub.asInterface( + ServiceManager.getService("window")) + .openSession(imm.getClient(), imm.getInputContext()); + mInitialized = true; + } catch (RemoteException e) { + } + } + } + + mThread = Thread.currentThread(); + mLocation = new WindowLeaked(null); + mLocation.fillInStackTrace(); + mWidth = -1; + mHeight = -1; + mDirty = new Rect(); + mTempRect = new Rect(); + mVisRect = new Rect(); + mVisPoint = new Point(); + mWinFrame = new Rect(); + mWindow = new W(this); + mInputMethodCallback = new InputMethodCallback(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; + mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this); + mViewConfiguration = ViewConfiguration.get(context); + mDensity = context.getResources().getDisplayMetrics().density; + } + + @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); + mSoftInputMode = attrs.softInputMode; + mWindowAttributesChanged = true; + mView = view; + mAttachInfo.mRootView = 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(), mAttachInfo.mContentInsets); + } catch (RemoteException e) { + mAdded = false; + mView = null; + mAttachInfo.mRootView = null; + unscheduleTraversals(); + throw new RuntimeException("Adding window failed", e); + } + mPendingContentInsets.set(mAttachInfo.mContentInsets); + mPendingVisibleInsets.set(0, 0, 0, 0); + if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow); + if (res < WindowManagerImpl.ADD_OKAY) { + mView = null; + mAttachInfo.mRootView = 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; + } + + void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { + synchronized (this) { + mWindowAttributes.copyFrom(attrs); + if (newView) { + mSoftInputMode = attrs.softInputMode; + requestLayout(); + } + 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); + if (mCurScrollY != 0) { + mTempRect.set(dirty); + mTempRect.offset(0, -mCurScrollY); + dirty = mTempRect; + } + 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!"); + } + // Note: don't apply scroll offset, because we want to know its + // visibility in the virtual canvas being given to the view hierarchy. + 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 = mWindowAttributes; + + 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 = lp; + } + + 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.mSurface = mSurface; + attachInfo.mHasWindowFocus = false; + attachInfo.mWindowVisibility = viewVisibility; + attachInfo.mRecomputeGlobalAttributes = false; + attachInfo.mKeepScreenOn = false; + viewVisibilityChanged = false; + host.dispatchAttachedToWindow(attachInfo, 0); + getRunQueue().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 (viewVisibility == View.GONE) { + // After making a window gone, we will count it as being + // shown for the first time the next time it gets focus. + mHasHadWindowFocus = false; + } + } + + boolean insetsChanged = false; + + if (mLayoutRequested) { + if (mFirst) { + host.fitSystemWindows(mAttachInfo.mContentInsets); + // make sure touch mode code executes by setting cached value + // to opposite of the added touch mode. + mAttachInfo.mInTouchMode = !mAddedTouchMode; + ensureTouchModeLocally(mAddedTouchMode); + } else { + if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) { + mAttachInfo.mContentInsets.set(mPendingContentInsets); + host.fitSystemWindows(mAttachInfo.mContentInsets); + insetsChanged = true; + if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " + + mAttachInfo.mContentInsets); + } + if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) { + mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " + + mAttachInfo.mVisibleInsets); + } + 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 || DEBUG_LAYOUT) 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 = lp; + //Log.i(TAG, "Keep screen on changed: " + attachInfo.mKeepScreenOn); + } + } + + if (mFirst || attachInfo.mViewVisibilityChanged) { + attachInfo.mViewVisibilityChanged = false; + int resizeMode = mSoftInputMode & + WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; + // If we are in auto resize mode, then we need to determine + // what mode to use now. + if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) { + final int N = attachInfo.mScrollContainers.size(); + for (int i=0; i<N; i++) { + if (attachInfo.mScrollContainers.get(i).isShown()) { + resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + } + } + if (resizeMode == 0) { + resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; + } + if ((lp.softInputMode & + WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) { + lp.softInputMode = (lp.softInputMode & + ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) | + resizeMode; + params = lp; + } + } + } + + 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); + + final boolean computesInternalInsets = + attachInfo.mTreeObserver.hasComputeInternalInsetsListeners(); + boolean insetsPending = false; + int relayoutResult = 0; + if (mFirst || windowShouldResize || insetsChanged + || viewVisibilityChanged || params != null) { + + if (viewVisibility == View.VISIBLE) { + // If this window is giving internal insets to the window + // manager, and it is being added or changing its visibility, + // then we want to first give the window manager "fake" + // insets to cause it to effectively ignore the content of + // the window during layout. This avoids it briefly causing + // other windows to resize/move based on the raw frame of the + // window, waiting until we can finish laying out this window + // and get back to the window manager with the ultimately + // computed insets. + insetsPending = computesInternalInsets + && (mFirst || viewVisibilityChanged); + + if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) { + if (params == null) { + params = mWindowAttributes; + } + mGlWanted = true; + } + } + + final Rect frame = mWinFrame; + boolean initialized = false; + boolean contentInsetsChanged = false; + boolean visibleInsetsChanged = 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, insetsPending, frame, + mPendingContentInsets, mPendingVisibleInsets, mSurface); + if (params != null) { + params.flags = fl; + } + + if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() + + " content=" + mPendingContentInsets.toShortString() + + " visible=" + mPendingVisibleInsets.toShortString() + + " surface=" + mSurface); + + contentInsetsChanged = !mPendingContentInsets.equals( + mAttachInfo.mContentInsets); + visibleInsetsChanged = !mPendingVisibleInsets.equals( + mAttachInfo.mVisibleInsets); + if (contentInsetsChanged) { + mAttachInfo.mContentInsets.set(mPendingContentInsets); + host.fitSystemWindows(mAttachInfo.mContentInsets); + if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " + + mAttachInfo.mContentInsets); + } + if (visibleInsetsChanged) { + mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " + + mAttachInfo.mVisibleInsets); + } + + if (!hadSurface) { + if (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; + } + } + } else if (!mSurface.isValid()) { + // If the surface has been removed, then reset the scroll + // positions. + mLastScrolledFocus = null; + mScrollY = mCurScrollY = 0; + if (mScroller != null) { + mScroller.abortAnimation(); + } + } + } 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 || contentInsetsChanged) { + childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); + childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); + + if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" + + mWidth + " measuredWidth=" + host.mMeasuredWidth + + " mHeight=" + mHeight + + " measuredHeight" + host.mMeasuredHeight + + " coveredInsetsChanged=" + contentInsetsChanged); + + // 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) { + if (DEBUG_LAYOUT) Log.v(TAG, + "And hey let's measure once more: width=" + width + + " height=" + height); + host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + mLayoutRequested = true; + } + } + + final boolean didLayout = mLayoutRequested; + boolean triggerGlobalLayoutListener = didLayout + || attachInfo.mRecomputeGlobalAttributes; + if (didLayout) { + mLayoutRequested = false; + mScrollMayChange = true; + if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v( + "ViewRoot", "Laying out " + 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(mTmpLocation); + mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1], + mTmpLocation[0] + host.mRight - host.mLeft, + mTmpLocation[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 (computesInternalInsets) { + ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets; + final Rect givenContent = attachInfo.mGivenInternalInsets.contentInsets; + final Rect givenVisible = attachInfo.mGivenInternalInsets.visibleInsets; + givenContent.left = givenContent.top = givenContent.right + = givenContent.bottom = givenVisible.left = givenVisible.top + = givenVisible.right = givenVisible.bottom = 0; + attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); + if (insetsPending || !mLastGivenInsets.equals(insets)) { + mLastGivenInsets.set(insets); + try { + sWindowSession.setInsets(mWindow, insets.mTouchableInsets, + insets.contentInsets, insets.visibleInsets); + } catch (RemoteException e) { + } + } + } + + if (mFirst) { + // handle first focus request + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()=" + + mView.hasFocus()); + if (mView != null) { + if (!mView.hasFocus()) { + mView.requestFocus(View.FOCUS_FORWARD); + mFocusedView = mRealFocusedView = mView.findFocus(); + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view=" + + mFocusedView); + } else { + mRealFocusedView = mView.findFocus(); + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view=" + + mRealFocusedView); + } + } + } + + mFirst = false; + mWillDrawSoon = false; + mNewSurfaceNeeded = false; + mViewVisibility = viewVisibility; + + if (mAttachInfo.mHasWindowFocus) { + final boolean imTarget = WindowManager.LayoutParams + .mayUseInputMethod(mWindowAttributes.flags); + if (imTarget != mLastWasImTarget) { + mLastWasImTarget = imTarget; + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && imTarget) { + imm.startGettingWindowFocus(mView); + imm.onWindowFocus(mView, mView.findFocus(), + mWindowAttributes.softInputMode, + !mHasHadWindowFocus, mWindowAttributes.flags); + } + } + } + + 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, remember 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; + } + + scrollToRectOrFocus(null, false); + + if (mAttachInfo.mViewScrollChanged) { + mAttachInfo.mViewScrollChanged = false; + mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); + } + + int yoff; + final boolean scrolling = mScroller != null + && mScroller.computeScrollOffset(); + if (scrolling) { + yoff = mScroller.getCurrY(); + } else { + yoff = mScrollY; + } + if (mCurScrollY != yoff) { + mCurScrollY = yoff; + fullRedrawNeeded = true; + } + + 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(); + canvas.translate(0, -yoff); + mView.mPrivateFlags |= View.DRAWN; + mView.draw(canvas); + canvas.translate(0, yoff); + + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + checkEglErrors(); + + if (SHOW_FPS) { + int now = (int)SystemClock.elapsedRealtime(); + if (sDrawTime != 0) { + nativeShowFPS(canvas, now - sDrawTime); + } + sDrawTime = now; + } + } + } + if (scrolling) { + mFullRedrawNeeded = true; + scheduleTraversals(); + } + 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()); + } + + Canvas canvas; + try { + canvas = surface.lockCanvas(dirty); + // TODO: Do this in native + canvas.setDensityScale(mDensity); + } 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; + } + + try { + if (!dirty.isEmpty()) { + long startTime; + + 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(0x00000000, PorterDuff.Mode.CLEAR); + } else if (yoff != 0) { + // If we are applying an offset, we need to clear the area + // where the offset doesn't appear to avoid having garbage + // left in the blank areas. + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } + + dirty.setEmpty(); + mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); + canvas.translate(0, -yoff); + mView.mPrivateFlags |= View.DRAWN; + mView.draw(canvas); + canvas.translate(0, yoff); + + if (SHOW_FPS) { + int now = (int)SystemClock.elapsedRealtime(); + if (sDrawTime != 0) { + nativeShowFPS(canvas, now - sDrawTime); + } + sDrawTime = now; + } + + if (PROFILE_DRAWING) { + EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + } + } + + } finally { + surface.unlockCanvasAndPost(canvas); + } + + if (LOCAL_LOGV) { + Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost"); + } + + if (scrolling) { + mFullRedrawNeeded = true; + scheduleTraversals(); + } + } + + boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) { + final View.AttachInfo attachInfo = mAttachInfo; + final Rect ci = attachInfo.mContentInsets; + final Rect vi = attachInfo.mVisibleInsets; + int scrollY = 0; + boolean handled = false; + + if (vi.left > ci.left || vi.top > ci.top + || vi.right > ci.right || vi.bottom > ci.bottom) { + // We'll assume that we aren't going to change the scroll + // offset, since we want to avoid that unless it is actually + // going to make the focus visible... otherwise we scroll + // all over the place. + scrollY = mScrollY; + // We can be called for two different situations: during a draw, + // to update the scroll position if the focus has changed (in which + // case 'rectangle' is null), or in response to a + // requestChildRectangleOnScreen() call (in which case 'rectangle' + // is non-null and we just want to scroll to whatever that + // rectangle is). + View focus = mRealFocusedView; + if (focus != mLastScrolledFocus) { + // If the focus has changed, then ignore any requests to scroll + // to a rectangle; first we want to make sure the entire focus + // view is visible. + rectangle = null; + } + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus + + " rectangle=" + rectangle + " ci=" + ci + + " vi=" + vi); + if (focus == mLastScrolledFocus && !mScrollMayChange + && rectangle == null) { + // Optimization: if the focus hasn't changed since last + // time, and no layout has happened, then just leave things + // as they are. + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Keeping scroll y=" + + mScrollY + " vi=" + vi.toShortString()); + } else if (focus != null) { + // We need to determine if the currently focused view is + // within the visible part of the window and, if not, apply + // a pan so it can be seen. + mLastScrolledFocus = focus; + mScrollMayChange = false; + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?"); + // Try to find the rectangle from the focus view. + if (focus.getGlobalVisibleRect(mVisRect, null)) { + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Root w=" + + mView.getWidth() + " h=" + mView.getHeight() + + " ci=" + ci.toShortString() + + " vi=" + vi.toShortString()); + if (rectangle == null) { + focus.getFocusedRect(mTempRect); + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Focus " + focus + + ": focusRect=" + mTempRect.toShortString()); + ((ViewGroup) mView).offsetDescendantRectToMyCoords( + focus, mTempRect); + if (DEBUG_INPUT_RESIZE) Log.v(TAG, + "Focus in window: focusRect=" + + mTempRect.toShortString() + + " visRect=" + mVisRect.toShortString()); + } else { + mTempRect.set(rectangle); + if (DEBUG_INPUT_RESIZE) Log.v(TAG, + "Request scroll to rect: " + + mTempRect.toShortString() + + " visRect=" + mVisRect.toShortString()); + } + if (mTempRect.intersect(mVisRect)) { + if (DEBUG_INPUT_RESIZE) Log.v(TAG, + "Focus window visible rect: " + + mTempRect.toShortString()); + if (mTempRect.height() > + (mView.getHeight()-vi.top-vi.bottom)) { + // If the focus simply is not going to fit, then + // best is probably just to leave things as-is. + if (DEBUG_INPUT_RESIZE) Log.v(TAG, + "Too tall; leaving scrollY=" + scrollY); + } else if ((mTempRect.top-scrollY) < vi.top) { + scrollY -= vi.top - (mTempRect.top-scrollY); + if (DEBUG_INPUT_RESIZE) Log.v(TAG, + "Top covered; scrollY=" + scrollY); + } else if ((mTempRect.bottom-scrollY) + > (mView.getHeight()-vi.bottom)) { + scrollY += (mTempRect.bottom-scrollY) + - (mView.getHeight()-vi.bottom); + if (DEBUG_INPUT_RESIZE) Log.v(TAG, + "Bottom covered; scrollY=" + scrollY); + } + handled = true; + } + } + } + } + + if (scrollY != mScrollY) { + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old=" + + mScrollY + " , new=" + scrollY); + if (!immediate) { + if (mScroller == null) { + mScroller = new Scroller(mView.getContext()); + } + mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY); + } else if (mScroller != null) { + mScroller.abortAnimation(); + } + mScrollY = scrollY; + } + + return handled; + } + + public void requestChildFocus(View child, View focused) { + checkThread(); + if (mFocusedView != focused) { + mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused); + scheduleTraversals(); + } + mFocusedView = mRealFocusedView = focused; + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Request child focus: focus now " + + mFocusedView); + } + + public void clearChildFocus(View child) { + checkThread(); + + View oldFocus = mFocusedView; + + if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Clearing child focus"); + mFocusedView = mRealFocusedView = 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; + mAttachInfo.mRootView = null; + + if (mUseGL) { + destroyGL(); + } + + try { + sWindowSession.remove(mWindow); + } catch (RemoteException e) { + } + } + + /** + * 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); + } + + + public final static int DO_TRAVERSAL = 1000; + public final static int DIE = 1001; + public final static int RESIZED = 1002; + public final static int RESIZED_REPORT = 1003; + public final static int WINDOW_FOCUS_CHANGED = 1004; + public final static int DISPATCH_KEY = 1005; + public final static int DISPATCH_POINTER = 1006; + public final static int DISPATCH_TRACKBALL = 1007; + public final static int DISPATCH_APP_VISIBILITY = 1008; + public final static int DISPATCH_GET_NEW_SURFACE = 1009; + public final static int FINISHED_EVENT = 1010; + public final static int DISPATCH_KEY_FROM_IME = 1011; + public final static int FINISH_INPUT_CONNECTION = 1012; + public final static int CHECK_FOCUS = 1013; + + @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: + final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj; + info.target.invalidate(info.left, info.top, info.right, info.bottom); + info.release(); + break; + case DO_TRAVERSAL: + if (mProfile) { + Debug.startMethodTracing("ViewRoot"); + } + + performTraversals(); + + if (mProfile) { + Debug.stopMethodTracing(); + mProfile = false; + } + break; + case FINISHED_EVENT: + handleFinishedEvent(msg.arg1, msg.arg2 != 0); + 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 = event.getAction() == MotionEvent.ACTION_OUTSIDE; + } + + 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); + } + if(Config.LOGV) { + captureMotionLog("captureDispatchPointer", event); + } + event.offsetLocation(0, mCurScrollY); + handled = mView.dispatchTouchEvent(event); + if (!handled && isDown) { + int edgeSlop = mViewConfiguration.getScaledEdgeSlop(); + + 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: + Rect coveredInsets = ((Rect[])msg.obj)[0]; + Rect visibleInsets = ((Rect[])msg.obj)[1]; + if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2 + && mPendingContentInsets.equals(coveredInsets) + && mPendingVisibleInsets.equals(visibleInsets)) { + break; + } + // fall through... + case RESIZED_REPORT: + if (mAdded) { + mWinFrame.left = 0; + mWinFrame.right = msg.arg1; + mWinFrame.top = 0; + mWinFrame.bottom = msg.arg2; + mPendingContentInsets.set(((Rect[])msg.obj)[0]); + mPendingVisibleInsets.set(((Rect[])msg.obj)[1]); + 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); + } + } + } + } + + mLastWasImTarget = WindowManager.LayoutParams + .mayUseInputMethod(mWindowAttributes.flags); + + InputMethodManager imm = InputMethodManager.peekInstance(); + if (mView != null) { + if (hasWindowFocus && imm != null && mLastWasImTarget) { + imm.startGettingWindowFocus(mView); + } + mView.dispatchWindowFocusChanged(hasWindowFocus); + } + + // Note: must be done after the focus change callbacks, + // so all of the view state is set up correctly. + if (hasWindowFocus) { + if (imm != null && mLastWasImTarget) { + imm.onWindowFocus(mView, mView.findFocus(), + mWindowAttributes.softInputMode, + !mHasHadWindowFocus, mWindowAttributes.flags); + } + // Clear the forward bit. We can just do this directly, since + // the window manager doesn't care about it. + mWindowAttributes.softInputMode &= + ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + ((WindowManager.LayoutParams)mView.getLayoutParams()) + .softInputMode &= + ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + mHasHadWindowFocus = true; + } + } + } break; + case DIE: + dispatchDetachedFromWindow(); + break; + case DISPATCH_KEY_FROM_IME: + if (LOCAL_LOGV) Log.v( + "ViewRoot", "Dispatching key " + + msg.obj + " from IME to " + mView); + deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false); + break; + case FINISH_INPUT_CONNECTION: { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.reportFinishInputConnection((InputConnection)msg.obj); + } + } break; + case CHECK_FOCUS: { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.checkFocus(); + } + } 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; + } + + if (DEBUG_TRACKBALL) Log.v(TAG, "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 something? + } + } + } finally { + if (handled) { + if (!didFinish) { + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + if (event != null) { + event.recycle(); + } + // If we reach this, we delivered a trackball event to mView and + // mView consumed it. Because we will not translate the trackball + // event into a key event, touch mode will not exit, so we exit + // touch mode here. + ensureTouchMode(false); + //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(), event.getEventTime(), "X"); + final float yOff = y.collect(event.getY(), event.getEventTime(), "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); + if (DEBUG_TRACKBALL) Log.v(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; + } + if ((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) { + 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; + } + + /** + * log motion events + */ + private static void captureMotionLog(String subTag, MotionEvent ev) { + //check dynamic switch + if (ev == null || + SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) { + return; + } + + StringBuilder sb = new StringBuilder(subTag + ": "); + sb.append(ev.getDownTime()).append(','); + sb.append(ev.getEventTime()).append(','); + sb.append(ev.getAction()).append(','); + sb.append(ev.getX()).append(','); + sb.append(ev.getY()).append(','); + sb.append(ev.getPressure()).append(','); + sb.append(ev.getSize()).append(','); + sb.append(ev.getMetaState()).append(','); + sb.append(ev.getXPrecision()).append(','); + sb.append(ev.getYPrecision()).append(','); + sb.append(ev.getDeviceId()).append(','); + sb.append(ev.getEdgeFlags()); + Log.d(TAG, sb.toString()); + } + /** + * log motion events + */ + private static void captureKeyLog(String subTag, KeyEvent ev) { + //check dynamic switch + if (ev == null || + SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) { + return; + } + StringBuilder sb = new StringBuilder(subTag + ": "); + sb.append(ev.getDownTime()).append(','); + sb.append(ev.getEventTime()).append(','); + sb.append(ev.getAction()).append(','); + sb.append(ev.getKeyCode()).append(','); + sb.append(ev.getRepeatCount()).append(','); + sb.append(ev.getMetaState()).append(','); + sb.append(ev.getDeviceId()).append(','); + sb.append(ev.getScanCode()); + Log.d(TAG, sb.toString()); + } + + int enqueuePendingEvent(Object event, boolean sendDone) { + int seq = mPendingEventSeq+1; + if (seq < 0) seq = 0; + mPendingEventSeq = seq; + mPendingEvents.put(seq, event); + return sendDone ? seq : -seq; + } + + Object retrievePendingEvent(int seq) { + if (seq < 0) seq = -seq; + Object event = mPendingEvents.get(seq); + if (event != null) { + mPendingEvents.remove(seq); + } + return event; + } + + private void deliverKeyEvent(KeyEvent event, boolean sendDone) { + // If mView is null, we just consume the key event because it doesn't + // make sense to do anything else with it. + boolean handled = mView != null + ? mView.dispatchKeyEventPreIme(event) : true; + if (handled) { + if (sendDone) { + if (LOCAL_LOGV) Log.v( + "ViewRoot", "Telling window manager key is finished"); + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } + return; + } + // If it is possible for this window to interact with the input + // method window, then we want to first dispatch our key events + // to the input method. + if (mLastWasImTarget) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null && mView != null) { + int seq = enqueuePendingEvent(event, sendDone); + if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" + + seq + " event=" + event); + imm.dispatchKeyEvent(mView.getContext(), seq, event, + mInputMethodCallback); + return; + } + } + deliverKeyEventToViewHierarchy(event, sendDone); + } + + void handleFinishedEvent(int seq, boolean handled) { + final KeyEvent event = (KeyEvent)retrievePendingEvent(seq); + if (DEBUG_IMF) Log.v(TAG, "IME finished event: seq=" + seq + + " handled=" + handled + " event=" + event); + if (event != null) { + final boolean sendDone = seq >= 0; + if (!handled) { + deliverKeyEventToViewHierarchy(event, sendDone); + return; + } else if (sendDone) { + if (LOCAL_LOGV) Log.v( + "ViewRoot", "Telling window manager key is finished"); + try { + sWindowSession.finishKey(mWindow); + } catch (RemoteException e) { + } + } else { + Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq + + " handled=" + handled + " ev=" + event + + ") neither delivering nor finishing key"); + } + } + } + + private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) { + try { + if (mView != null && mAdded) { + final int action = event.getAction(); + boolean isDown = (action == KeyEvent.ACTION_DOWN); + + if (checkForLeavingTouchModeAndConsume(event)) { + return; + } + + if (Config.LOGV) { + captureKeyLog("captureDispatchKeyEvent", event); + } + boolean keyHandled = mView.dispatchKeyEvent(event); + + if (!keyHandled && isDown) { + 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 boolean performHapticFeedback(int effectId, boolean always) { + try { + return sWindowSession.performHapticFeedback(mWindow, effectId, always); + } catch (RemoteException e) { + return false; + } + } + + /** + * {@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, false, mWinFrame, mPendingContentInsets, + mPendingVisibleInsets, mSurface) + &WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { + sWindowSession.finishDrawing(mWindow); + } + } catch (RemoteException e) { + } + } + + mSurface = null; + } + if (mAdded) { + mAdded = false; + if (immediate) { + dispatchDetachedFromWindow(); + } else if (mView != null) { + sendEmptyMessage(DIE); + } + } + } + } + + public void dispatchFinishedEvent(int seq, boolean handled) { + Message msg = obtainMessage(FINISHED_EVENT); + msg.arg1 = seq; + msg.arg2 = handled ? 1 : 0; + sendMessage(msg); + } + + public void dispatchResized(int w, int h, Rect coveredInsets, + Rect visibleInsets, boolean reportDraw) { + if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w + + " h=" + h + " coveredInsets=" + coveredInsets.toShortString() + + " visibleInsets=" + visibleInsets.toShortString() + + " reportDraw=" + reportDraw); + Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED); + msg.arg1 = w; + msg.arg2 = h; + msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) }; + 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 + } + + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, + boolean immediate) { + return scrollToRectOrFocus(rectangle, immediate); + } + + static class InputMethodCallback extends IInputMethodCallback.Stub { + private WeakReference<ViewRoot> mViewRoot; + + public InputMethodCallback(ViewRoot viewRoot) { + mViewRoot = new WeakReference<ViewRoot>(viewRoot); + } + + public void finishedEvent(int seq, boolean handled) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchFinishedEvent(seq, handled); + } + } + + public void sessionCreated(IInputMethodSession session) throws RemoteException { + // Stub -- not for use in the client. + } + } + + 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, Rect coveredInsets, + Rect visibleInsets, boolean reportDraw) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchResized(w, h, coveredInsets, + visibleInsets, reportDraw); + } + } + + public void dispatchKey(KeyEvent event) { + final ViewRoot viewRoot = mViewRoot.get(); + if (viewRoot != null) { + viewRoot.dispatchKey(event); + } else { + Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!"); + } + } + + 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 { + /** + * The maximum amount of acceleration we will apply. + */ + static final float MAX_ACCELERATION = 20; + + /** + * The maximum amount of time (in milliseconds) between events in order + * for us to consider the user to be doing fast trackball movements, + * and thus apply an acceleration. + */ + static final long FAST_MOVE_TIME = 150; + + /** + * Scaling factor to the time (in milliseconds) between events to how + * much to multiple/divide the current acceleration. When movement + * is < FAST_MOVE_TIME this multiplies the acceleration; when > + * FAST_MOVE_TIME it divides it. + */ + static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40); + + float position; + float absPosition; + float acceleration = 1; + long lastMoveTime = 0; + int step; + int dir; + int nonAccelMovement; + + void reset(int _step) { + position = 0; + acceleration = 1; + lastMoveTime = 0; + 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, long time, String axis) { + long normTime; + if (off > 0) { + normTime = (long)(off * FAST_MOVE_TIME); + if (dir < 0) { + if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!"); + position = 0; + step = 0; + acceleration = 1; + lastMoveTime = 0; + } + dir = 1; + } else if (off < 0) { + normTime = (long)((-off) * FAST_MOVE_TIME); + if (dir > 0) { + if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!"); + position = 0; + step = 0; + acceleration = 1; + lastMoveTime = 0; + } + dir = -1; + } else { + normTime = 0; + } + + // The number of milliseconds between each movement that is + // considered "normal" and will not result in any acceleration + // or deceleration, scaled by the offset we have here. + if (normTime > 0) { + long delta = time - lastMoveTime; + lastMoveTime = time; + float acc = acceleration; + if (delta < normTime) { + // The user is scrolling rapidly, so increase acceleration. + float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR; + if (scale > 1) acc *= scale; + if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off=" + + off + " normTime=" + normTime + " delta=" + delta + + " scale=" + scale + " acc=" + acc); + acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION; + } else { + // The user is scrolling slowly, so decrease acceleration. + float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR; + if (scale > 1) acc /= scale; + if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off=" + + off + " normTime=" + normTime + " delta=" + delta + + " scale=" + scale + " acc=" + acc); + acceleration = acc > 1 ? acc : 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. This is a simple + // acceleration on top of what we already compute based + // on how quickly the wheel is being turned, to apply + // a longer increasing acceleration to continuous movement + // in one direction. + 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 < MAX_ACCELERATION ? acc : acceleration; + break; + } + } while (true); + } + } + + public static final class CalledFromWrongThreadException extends AndroidRuntimeException { + public CalledFromWrongThreadException(String msg) { + super(msg); + } + } + + 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; + } + }; + + static RunQueue getRunQueue() { + RunQueue rq = sRunQueues.get(); + if (rq != null) { + return rq; + } + rq = new RunQueue(); + sRunQueues.set(rq); + return rq; + } + + /** + * @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; + + 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) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HandlerAction that = (HandlerAction) o; + + return !(action != null ? !action.equals(that.action) : that.action != null); + + } + + @Override + public int hashCode() { + int result = action != null ? action.hashCode() : 0; + result = 31 * result + (int) (delay ^ (delay >>> 32)); + return result; + } + } + } + + 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..47b52e4 --- /dev/null +++ b/core/java/android/view/ViewTreeObserver.java @@ -0,0 +1,613 @@ +/* + * 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 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 ArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; + private ArrayList<OnScrollChangedListener> mOnScrollChangedListeners; + + 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); + } + + /** + * Interface definition for a callback to be invoked when + * something in the view tree has been scrolled. + * + * @hide pending API council approval + */ + public interface OnScrollChangedListener { + /** + * Callback method to be invoked when something in the view tree + * has been scrolled. + */ + public void onScrollChanged(); + } + + /** + * Parameters used with OnComputeInternalInsetsListener. + * {@hide pending API Council approval} + */ + public final static class InternalInsetsInfo { + /** + * Offsets from the frame of the window at which the content of + * windows behind it should be placed. + */ + public final Rect contentInsets = new Rect(); + + /** + * Offsets from the fram of the window at which windows behind it + * are visible. + */ + public final Rect visibleInsets = new Rect(); + + /** + * Option for {@link #setTouchableInsets(int)}: the entire window frame + * can be touched. + */ + public static final int TOUCHABLE_INSETS_FRAME = 0; + + /** + * Option for {@link #setTouchableInsets(int)}: the area inside of + * the content insets can be touched. + */ + public static final int TOUCHABLE_INSETS_CONTENT = 1; + + /** + * Option for {@link #setTouchableInsets(int)}: the area inside of + * the visible insets can be touched. + */ + public static final int TOUCHABLE_INSETS_VISIBLE = 2; + + /** + * Set which parts of the window can be touched: either + * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT}, + * or {@link #TOUCHABLE_INSETS_VISIBLE}. + */ + public void setTouchableInsets(int val) { + mTouchableInsets = val; + } + + public int getTouchableInsets() { + return mTouchableInsets; + } + + int mTouchableInsets; + + void reset() { + final Rect givenContent = contentInsets; + final Rect givenVisible = visibleInsets; + givenContent.left = givenContent.top = givenContent.right + = givenContent.bottom = givenVisible.left = givenVisible.top + = givenVisible.right = givenVisible.bottom = 0; + mTouchableInsets = TOUCHABLE_INSETS_FRAME; + } + + @Override public boolean equals(Object o) { + try { + if (o == null) { + return false; + } + InternalInsetsInfo other = (InternalInsetsInfo)o; + if (!contentInsets.equals(other.contentInsets)) { + return false; + } + if (!visibleInsets.equals(other.visibleInsets)) { + return false; + } + return mTouchableInsets == other.mTouchableInsets; + } catch (ClassCastException e) { + return false; + } + } + + void set(InternalInsetsInfo other) { + contentInsets.set(other.contentInsets); + visibleInsets.set(other.visibleInsets); + mTouchableInsets = other.mTouchableInsets; + } + } + + /** + * Interface definition for a callback to be invoked when layout has + * completed and the client can compute its interior insets. + * {@hide pending API Council approval} + */ + public interface OnComputeInternalInsetsListener { + /** + * Callback method to be invoked when layout has completed and the + * client can compute its interior insets. + * + * @param inoutInfo Should be filled in by the implementation with + * the information about the insets of the window. This is called + * with whatever values the previous OnComputeInternalInsetsListener + * returned, if there are multiple such listeners in the window. + */ + public void onComputeInternalInsets(InternalInsetsInfo inoutInfo); + } + + /** + * 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; + } + } + + if (observer.mOnComputeInternalInsetsListeners != null) { + if (mOnComputeInternalInsetsListeners != null) { + mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners); + } else { + mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners; + } + } + + 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 a view has been scrolled. + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @hide pending API council approval + */ + public void addOnScrollChangedListener(OnScrollChangedListener listener) { + checkIsAlive(); + + if (mOnScrollChangedListeners == null) { + mOnScrollChangedListeners = new ArrayList<OnScrollChangedListener>(); + } + + mOnScrollChangedListeners.add(listener); + } + + /** + * Remove a previously installed scroll-changed callback + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnScrollChangedListener(OnScrollChangedListener) + * + * @hide pending API council approval + */ + public void removeOnScrollChangedListener(OnScrollChangedListener victim) { + checkIsAlive(); + if (mOnScrollChangedListeners == null) { + return; + } + mOnScrollChangedListeners.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); + } + + /** + * Register a callback to be invoked when the invoked when it is time to + * compute the window's internal insets. + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * {@hide pending API Council approval} + */ + public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) { + checkIsAlive(); + + if (mOnComputeInternalInsetsListeners == null) { + mOnComputeInternalInsetsListeners = new ArrayList<OnComputeInternalInsetsListener>(); + } + + mOnComputeInternalInsetsListeners.add(listener); + } + + /** + * Remove a previously installed internal insets computation callback + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener) + * {@hide pending API Council approval} + */ + public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) { + checkIsAlive(); + if (mOnComputeInternalInsetsListeners == null) { + return; + } + mOnComputeInternalInsetsListeners.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); + } + } + } + + /** + * Notifies registered listeners that something has scrolled. + */ + final void dispatchOnScrollChanged() { + final ArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners; + + if (listeners != null) { + for (OnScrollChangedListener scl : mOnScrollChangedListeners) { + scl.onScrollChanged(); + } + } + } + + /** + * Returns whether there are listeners for computing internal insets. + */ + final boolean hasComputeInternalInsetsListeners() { + final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; + return (listeners != null && listeners.size() > 0); + } + + /** + * Calls all listeners to compute the current insets. + */ + final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) { + final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; + if (listeners != null) { + final int count = listeners.size(); + for (int i = count - 1; i >= 0; i--) { + listeners.get(i).onComputeInternalInsets(inoutInfo); + } + } + } +} diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java new file mode 100644 index 0000000..a573983 --- /dev/null +++ b/core/java/android/view/VolumePanel.java @@ -0,0 +1,410 @@ +/* + * 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.bluetooth.HeadsetBase; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.AudioService; +import android.media.AudioSystem; +import android.media.ToneGenerator; +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 static final int RINGTONE_VOLUME_TEXT = com.android.internal.R.string.volume_ringtone; + private static final int MUSIC_VOLUME_TEXT = com.android.internal.R.string.volume_music; + private static final int INCALL_VOLUME_TEXT = com.android.internal.R.string.volume_call; + private static final int ALARM_VOLUME_TEXT = com.android.internal.R.string.volume_alarm; + private static final int UNKNOWN_VOLUME_TEXT = com.android.internal.R.string.volume_unknown; + private static final int NOTIFICATION_VOLUME_TEXT = + com.android.internal.R.string.volume_notification; + private static final int BLUETOOTH_INCALL_VOLUME_TEXT = + com.android.internal.R.string.volume_bluetooth_call; + + protected Context mContext; + private AudioManager mAudioManager; + protected AudioService mAudioService; + + private final Toast mToast; + private final View mView; + private final TextView mMessage; + private final TextView mAdditionalMessage; + private final ImageView mSmallStreamIcon; + private final ImageView mLargeStreamIcon; + private final ProgressBar mLevel; + + // Synchronize when accessing this + private ToneGenerator mToneGenerators[]; + private Vibrator mVibrator; + + public VolumePanel(Context context, AudioService volumeService) { + mContext = context; + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + mAudioService = volumeService; + mToast = new Toast(context); + + 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); + mAdditionalMessage = + (TextView) view.findViewById(com.android.internal.R.id.additional_message); + mSmallStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon); + mLargeStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon); + mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level); + + mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; + 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); + int message = UNKNOWN_VOLUME_TEXT; + int additionalMessage = 0; + + if (LOGD) { + Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType + + ", flags: " + flags + "), index: " + index); + } + + // get max volume for progress bar + int max = mAudioService.getStreamMaxVolume(streamType); + + switch (streamType) { + + case AudioManager.STREAM_RING: { + message = RINGTONE_VOLUME_TEXT; + setRingerIcon(index); + break; + } + + case AudioManager.STREAM_MUSIC: { + message = MUSIC_VOLUME_TEXT; + if (mAudioManager.isBluetoothA2dpOn()) { + additionalMessage = + com.android.internal.R.string.volume_music_hint_playing_through_bluetooth; + setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_ad2p); + } else { + setSmallIcon(index); + } + break; + } + + case AudioManager.STREAM_VOICE_CALL: { + /* + * For in-call voice call volume, there is no inaudible volume. + * Rescale the UI control so the progress bar doesn't go all + * the way to zero and don't show the mute icon. + */ + index++; + max++; + message = INCALL_VOLUME_TEXT; + setSmallIcon(index); + break; + } + + case AudioManager.STREAM_ALARM: { + message = ALARM_VOLUME_TEXT; + setSmallIcon(index); + break; + } + + case AudioManager.STREAM_NOTIFICATION: { + message = NOTIFICATION_VOLUME_TEXT; + setSmallIcon(index); + break; + } + + case AudioManager.STREAM_BLUETOOTH_SCO: { + /* + * For in-call voice call volume, there is no inaudible volume. + * Rescale the UI control so the progress bar doesn't go all + * the way to zero and don't show the mute icon. + */ + index++; + max++; + message = BLUETOOTH_INCALL_VOLUME_TEXT; + setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_in_call); + break; + } + } + + String messageString = Resources.getSystem().getString(message); + if (!mMessage.getText().equals(messageString)) { + mMessage.setText(messageString); + } + + if (additionalMessage == 0) { + mAdditionalMessage.setVisibility(View.GONE); + } else { + mAdditionalMessage.setVisibility(View.VISIBLE); + mAdditionalMessage.setText(Resources.getSystem().getString(additionalMessage)); + } + + 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) { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int i = numStreamTypes - 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]; + } + } + } + + /** + * Makes the small icon visible, and hides the large icon. + * + * @param index The volume index, where 0 means muted. + */ + private void setSmallIcon(int index) { + mLargeStreamIcon.setVisibility(View.GONE); + mSmallStreamIcon.setVisibility(View.VISIBLE); + + mSmallStreamIcon.setImageResource(index == 0 + ? com.android.internal.R.drawable.ic_volume_off_small + : com.android.internal.R.drawable.ic_volume_small); + } + + /** + * Makes the large image view visible with the given icon. + * + * @param resId The icon to display. + */ + private void setLargeIcon(int resId) { + mSmallStreamIcon.setVisibility(View.GONE); + mLargeStreamIcon.setVisibility(View.VISIBLE); + mLargeStreamIcon.setImageResource(resId); + } + + /** + * Makes the ringer icon visible with an icon that is chosen + * based on the current ringer mode. + * + * @param index + */ + private void setRingerIcon(int index) { + mSmallStreamIcon.setVisibility(View.GONE); + mLargeStreamIcon.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; + } + mLargeStreamIcon.setImageResource(icon); + } + + protected void onFreeResources() { + // We'll keep the views, just ditch the cached drawable and hence + // bitmaps + mSmallStreamIcon.setImageDrawable(null); + mLargeStreamIcon.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..428de67 --- /dev/null +++ b/core/java/android/view/Window.java @@ -0,0 +1,1005 @@ +/* + * 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; +import android.util.Log; + +/** + * 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; + + private boolean mHasSoftInputMode = false; + + // 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.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); + } + } + + /** + * Specify custom animations to use for the window, as per + * {@link WindowManager.LayoutParams#windowAnimations + * WindowManager.LayoutParams.windowAnimations}. Providing anything besides + * 0 here will override the animations the window would + * normally retrieve from its theme. + */ + public void setWindowAnimations(int resId) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.windowAnimations = resId; + if (mCallback != null) { + mCallback.onWindowAttributesChanged(attrs); + } + } + + /** + * Specify an explicit soft input mode to use for the window, as per + * {@link WindowManager.LayoutParams#softInputMode + * WindowManager.LayoutParams.softInputMode}. Providing anything besides + * "unspecified" here will override the input mode the window would + * normally retrieve from its theme. + */ + public void setSoftInputMode(int mode) { + final WindowManager.LayoutParams attrs = getAttributes(); + if (mode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { + attrs.softInputMode = mode; + mHasSoftInputMode = true; + } else { + mHasSoftInputMode = 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. <strong>PLEASE NOTE:</strong> the + * layout params you give here should generally be from values previously + * retrieved with {@link #getAttributes()}; you probably do not want to + * blindly create and apply your own, since this will blow away any values + * set by the framework that you are not interested in. + * + * @param a The new window attributes, which will completely override any + * current values. + */ + public void setAttributes(WindowManager.LayoutParams a) { + mWindowAttributes.copyFrom(a); + 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; + } + + /** + * Has the app specified their own soft input mode? + */ + protected final boolean hasSoftInputMode() { + return mHasSoftInputMode; + } + + /** + * 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..b87cc42 --- /dev/null +++ b/core/java/android/view/WindowManager.java @@ -0,0 +1,959 @@ +/* + * 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.pm.ActivityInfo; +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_APPLICATION_ATTACHED_DIALOG + * @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 + * @see #TYPE_STATUS_BAR_PANEL + * @see #TYPE_SYSTEM_DIALOG + * @see #TYPE_KEYGUARD_DIALOG + * @see #TYPE_SYSTEM_ERROR + * @see #TYPE_INPUT_METHOD + * @see #TYPE_INPUT_METHOD_DIALOG + */ + 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; + + /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout + * of the window happens as that of a top-level window, <em>not</em> + * as a child of its container. + */ + public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3; + + /** + * 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; + + /** + * Window type: internal input methods windows, which appear above + * the normal UI. Application windows may be resized or panned to keep + * the input focus visible while this window is displayed. + */ + public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11; + + /** + * Window type: internal input methods dialog windows, which appear above + * the current input method window. + */ + public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12; + + /** + * 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 key input focus, so the + * user can not send key or other button events to it. Those will + * instead go to whatever focusable window is behind it. This flag + * will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that + * is explicitly set. + * + * <p>Setting this flag also implies that the window will not need to + * interact with + * a soft input method, so it will be Z-ordered and positioned + * independently of any active input method (typically this means it + * gets Z-ordered on top of the input method, so it can use the full + * screen for its content and cover the input method if needed. You + * can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */ + 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: invert the state of {@link #FLAG_NOT_FOCUSABLE} with + * respect to how this window interacts with the current method. That + * is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the + * window will behave as if it needs to interact with the input method + * and thus be placed behind/away from it; if FLAG_NOT_FOCUSABLE is + * not set and this flag is set, then the window will behave as if it + * doesn't need to interact with the input method and can be placed + * to use more space and cover the input method. + */ + public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000; + + /** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you + * can set this flag to receive a single special MotionEvent with + * the action + * {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE} for + * touches that occur outside of your window. Note that you will not + * receive the full down/move/up gesture, only the location of the + * first down as an ACTION_OUTSIDE. + */ + public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000; + + /** 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; + + /** + * Given a particular set of window manager flags, determine whether + * such a window may be a target for an input method when it has + * focus. In particular, this checks the + * {@link #FLAG_NOT_FOCUSABLE} and {@link #FLAG_ALT_FOCUSABLE_IM} + * flags and returns true if the combination of the two corresponds + * to a window that needs to be behind the input method so that the + * user can type into it. + * + * @param flags The current window manager flags. + * + * @return Returns true if such a window should be behind/interact + * with an input method, false if not. + */ + public static boolean mayUseInputMethod(int flags) { + switch (flags&(FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)) { + case 0: + case FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM: + return true; + } + return false; + } + + /** + * Mask for {@link #softInputMode} of the bits that determine the + * desired visibility state of the soft input area for this window. + */ + public static final int SOFT_INPUT_MASK_STATE = 0x0f; + + /** + * Visibility state for {@link #softInputMode}: no state has been specified. + */ + public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0; + + /** + * Visibility state for {@link #softInputMode}: please don't change the state of + * the soft input area. + */ + public static final int SOFT_INPUT_STATE_UNCHANGED = 1; + + /** + * Visibility state for {@link #softInputMode}: please hide any soft input + * area when normally appropriate (when the user is navigating + * forward to your window). + */ + public static final int SOFT_INPUT_STATE_HIDDEN = 2; + + /** + * Visibility state for {@link #softInputMode}: please always hide any + * soft input area when this window receives focus. + */ + public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3; + + /** + * Visibility state for {@link #softInputMode}: please show the soft + * input area when normally appropriate (when the user is navigating + * forward to your window). + */ + public static final int SOFT_INPUT_STATE_VISIBLE = 4; + + /** + * Visibility state for {@link #softInputMode}: please always make the + * soft input area visible when this window receives input focus. + */ + public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5; + + /** + * Mask for {@link #softInputMode} of the bits that determine the + * way that the window should be adjusted to accommodate the soft + * input window. + */ + public static final int SOFT_INPUT_MASK_ADJUST = 0xf0; + + /** Adjustment option for {@link #softInputMode}: nothing specified. + * The system will try to pick one or + * the other depending on the contents of the window. + */ + public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00; + + /** Adjustment option for {@link #softInputMode}: set to allow the + * window to be resized when an input + * method is shown, so that its contents are not covered by the input + * method. This can <em>not<em> be combined with + * {@link #SOFT_INPUT_ADJUST_PAN}; if + * neither of these are set, then the system will try to pick one or + * the other depending on the contents of the window. + */ + public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10; + + /** Adjustment option for {@link #softInputMode}: set to have a window + * pan when an input method is + * shown, so it doesn't need to deal with resizing but just panned + * by the framework to ensure the current input focus is visible. This + * can <em>not<em> be combined with {@link #SOFT_INPUT_ADJUST_RESIZE}; if + * neither of these are set, then the system will try to pick one or + * the other depending on the contents of the window. + */ + public static final int SOFT_INPUT_ADJUST_PAN = 0x20; + + /** + * Bit for {@link #softInputMode}: set when the user has navigated + * forward to the window. This is normally set automatically for + * you by the system, though you may want to set it in certain cases + * when you are displaying a window yourself. This flag will always + * be cleared automatically after the window is displayed. + */ + public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100; + + /** + * Desired operating mode for any soft input area. May any combination + * of: + * + * <ul> + * <li> One of the visibility states + * {@link #SOFT_INPUT_STATE_UNSPECIFIED}, {@link #SOFT_INPUT_STATE_UNCHANGED}, + * {@link #SOFT_INPUT_STATE_HIDDEN}, {@link #SOFT_INPUT_STATE_ALWAYS_VISIBLE}, or + * {@link #SOFT_INPUT_STATE_VISIBLE}. + * <li> One of the adjustment options + * {@link #SOFT_INPUT_ADJUST_UNSPECIFIED}, + * {@link #SOFT_INPUT_ADJUST_RESIZE}, or + * {@link #SOFT_INPUT_ADJUST_PAN}. + */ + public int softInputMode; + + /** + * 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; + + /** + * This can be used to override the user's preferred brightness of + * the screen. A value of less than 0, the default, means to use the + * preferred screen brightness. 0 to 1 adjusts the brightness from + * dark to full bright. + */ + public float screenBrightness = -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; + + /** + * Specific orientation value for a window. + * May be any of the same values allowed + * for {@link android.content.pm.ActivityInfo#screenOrientation}. + * If not set, a default value of + * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} + * will be used. + */ + public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + + + 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(softInputMode); + out.writeInt(gravity); + out.writeFloat(horizontalMargin); + out.writeFloat(verticalMargin); + out.writeInt(format); + out.writeInt(windowAnimations); + out.writeFloat(alpha); + out.writeFloat(dimAmount); + out.writeFloat(screenBrightness); + out.writeStrongBinder(token); + out.writeString(packageName); + TextUtils.writeToParcel(mTitle, out, parcelableFlags); + out.writeInt(screenOrientation); + } + + 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(); + softInputMode = in.readInt(); + gravity = in.readInt(); + horizontalMargin = in.readFloat(); + verticalMargin = in.readFloat(); + format = in.readInt(); + windowAnimations = in.readInt(); + alpha = in.readFloat(); + dimAmount = in.readFloat(); + screenBrightness = in.readFloat(); + token = in.readStrongBinder(); + packageName = in.readString(); + mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + screenOrientation = in.readInt(); + } + + 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 static final int SOFT_INPUT_MODE_CHANGED = 1<<9; + public static final int SCREEN_ORIENTATION_CHANGED = 1<<10; + public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11; + + 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 (horizontalWeight != o.horizontalWeight) { + horizontalWeight = o.horizontalWeight; + changes |= LAYOUT_CHANGED; + } + if (verticalWeight != o.verticalWeight) { + verticalWeight = o.verticalWeight; + changes |= LAYOUT_CHANGED; + } + if (horizontalMargin != o.horizontalMargin) { + horizontalMargin = o.horizontalMargin; + changes |= LAYOUT_CHANGED; + } + if (verticalMargin != o.verticalMargin) { + verticalMargin = o.verticalMargin; + 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 (softInputMode != o.softInputMode) { + softInputMode = o.softInputMode; + changes |= SOFT_INPUT_MODE_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; + } + if (screenBrightness != o.screenBrightness) { + screenBrightness = o.screenBrightness; + changes |= SCREEN_BRIGHTNESS_CHANGED; + } + + if (screenOrientation != o.screenOrientation) { + screenOrientation = o.screenOrientation; + changes |= SCREEN_ORIENTATION_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() { + StringBuilder sb = new StringBuilder(256); + sb.append("WM.LayoutParams{"); + sb.append("("); + sb.append(x); + sb.append(','); + sb.append(y); + sb.append(")("); + sb.append((width==FILL_PARENT?"fill":(width==WRAP_CONTENT?"wrap":width))); + sb.append('x'); + sb.append((height==FILL_PARENT?"fill":(height==WRAP_CONTENT?"wrap":height))); + sb.append(")"); + if (softInputMode != 0) { + sb.append(" sim=#"); + sb.append(Integer.toHexString(softInputMode)); + } + if (gravity != 0) { + sb.append(" gr=#"); + sb.append(Integer.toHexString(gravity)); + } + sb.append(" ty="); + sb.append(type); + sb.append(" fl=#"); + sb.append(Integer.toHexString(flags)); + sb.append(" fmt="); + sb.append(format); + if (windowAnimations != 0) { + sb.append(" wanim=0x"); + sb.append(Integer.toHexString(windowAnimations)); + } + if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { + sb.append(" or="); + sb.append(screenOrientation); + } + sb.append('}'); + return sb.toString(); + } + + 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..755d7b8 --- /dev/null +++ b/core/java/android/view/WindowManagerImpl.java @@ -0,0 +1,364 @@ +/* + * 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; +import android.view.inputmethod.InputMethodManager; + +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, true); + 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(view.getContext()); + 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, false); + } + } + + 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; + } + + InputMethodManager imm = InputMethodManager.getInstance(view.getContext()); + if (imm != null) { + imm.windowDismissed(mViews[index].getWindowToken()); + } + 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 WindowManager.LayoutParams getRootViewLayoutParameter(View view) { + ViewParent vp = view.getParent(); + while (vp != null && !(vp instanceof ViewRoot)) { + vp = vp.getParent(); + } + + if (vp == null) return null; + + ViewRoot vr = (ViewRoot)vp; + + int N = mRoots.length; + for (int i = 0; i < N; ++i) { + if (mRoots[i] == vr) { + return mParams[i]; + } + } + + return null; + } + + 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..0f15b17 --- /dev/null +++ b/core/java/android/view/WindowManagerPolicy.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; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.LocalPowerManager; +import android.view.animation.Animation; + +/** + * 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 parentFrame The frame of the parent container this window + * is in, used for computing its basic position. + * @param displayFrame The frame of the overall display in which this + * window can appear, used for constraining the overall dimensions + * of the window. + * @param contentFrame The frame within the display in which we would + * like active content to appear. This will cause windows behind to + * be resized to match the given content frame. + * @param visibleFrame The frame within the display that the window + * is actually visible, used for computing its visible insets to be + * given to windows behind. + * This can be used as a hint for scrolling (avoiding resizing) + * the window to make certain that parts of its content + * are visible. + */ + public void computeFrameLw(Rect parentFrame, Rect displayFrame, + Rect contentFrame, Rect visibleFrame); + + /** + * Retrieve the current frame of the window that has been assigned by + * the window manager. Must be called with the window manager lock held. + * + * @return Rect The rectangle holding the window frame. + */ + public Rect getFrameLw(); + + /** + * Retrieve the current frame of the window that is actually shown. + * Must be called with the window manager lock held. + * + * @return Rect The rectangle holding the shown window frame. + */ + public Rect getShownFrameLw(); + + /** + * Retrieve the frame of the display that this window was last + * laid out in. Must be called with the + * window manager lock held. + * + * @return Rect The rectangle holding the display frame. + */ + public Rect getDisplayFrameLw(); + + /** + * Retrieve the frame of the content area that this window was last + * laid out in. This is the area in which the content of the window + * should be placed. It will be smaller than the display frame to + * account for screen decorations such as a status bar or soft + * keyboard. Must be called with the + * window manager lock held. + * + * @return Rect The rectangle holding the content frame. + */ + public Rect getContentFrameLw(); + + /** + * Retrieve the frame of the visible area that this window was last + * laid out in. This is the area of the screen in which the window + * will actually be fully visible. It will be smaller than the + * content frame to account for transient UI elements blocking it + * such as an input method's candidates UI. Must be called with the + * window manager lock held. + * + * @return Rect The rectangle holding the visible frame. + */ + public Rect getVisibleFrameLw(); + + /** + * Returns true if this window is waiting to receive its given + * internal insets from the client app, and so should not impact the + * layout of other windows. + */ + public boolean getGivenInsetsPendingLw(); + + /** + * Retrieve the insets given by this window's client for the content + * area of windows behind it. Must be called with the + * window manager lock held. + * + * @return Rect The left, top, right, and bottom insets, relative + * to the window's frame, of the actual contents. + */ + public Rect getGivenContentInsetsLw(); + + /** + * Retrieve the insets given by this window's client for the visible + * area of windows behind it. Must be called with the + * window manager lock held. + * + * @return Rect The left, top, right, and bottom insets, relative + * to the window's frame, of the actual visible area. + */ + public Rect getGivenVisibleInsetsLw(); + + /** + * Retrieve the current LayoutParams of the window. + * + * @return WindowManager.LayoutParams The window's internal LayoutParams + * instance. + */ + public WindowManager.LayoutParams getAttrs(); + + /** + * Get the layer at which this window's surface will be Z-ordered. + */ + public int getSurfaceLayer(); + + /** + * 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 visible? It is not visible if there is no + * surface, or we are in the process of running an exit animation + * that will remove the surface. + */ + boolean isVisibleLw(); + + /** + * 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. + * @param onlyOpaque If true, this will only pass if the window is + * also opaque. + * @return Returns true if the window is both full screen and opaque + */ + public boolean fillsScreenLw(int width, int height, boolean shownFrame, + boolean onlyOpaque); + + /** + * 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. + * Returns true if {@link #showLw} was last called for the window. + */ + public boolean hideLw(boolean doAnimation); + + /** + * 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. + * Returns true if {@link #hideLw} was last called for the window. + */ + public boolean showLw(boolean doAnimation); + } + + /** 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 most recent layout, so they are not guaranteed to + * be correct. + * + * @param attrs The LayoutParams of the window. + * @param contentInset The areas covered by system windows, expressed as positive insets + * + */ + public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset); + + /** + * 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, int lastRotation, + boolean displayEnabled); + + /** + * Called when the system is mostly done booting to dentermine whether + * the system should go into safe mode. + */ + public boolean detectSafeMode(); + + /** + * 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); + + /** + * Call from application to perform haptic feedback on its window. + */ + public boolean performHapticFeedback(WindowState win, int effectId, boolean always); + + /** + * Called when we have stopped keeping the screen on because a window + * requesting this is no longer visible. + */ + public void screenOnStopped(); +} diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java new file mode 100755 index 0000000..5877932 --- /dev/null +++ b/core/java/android/view/WindowOrientationListener.java @@ -0,0 +1,180 @@ +/* + * 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.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.Config; +import android.util.Log; + +/** + * A special helper class used by the WindowManager + * for receiving notifications from the SensorManager when + * the orientation of the device has changed. + * @hide + */ +public abstract class WindowOrientationListener { + private static final String TAG = "WindowOrientationListener"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private int mOrientation = ORIENTATION_UNKNOWN; + private SensorManager mSensorManager; + private boolean mEnabled = false; + private int mRate; + private Sensor mSensor; + private SensorEventListener mSensorEventListener; + + /** + * 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; + /* + * Returned when the device is almost lying flat on a surface + */ + public static final int ORIENTATION_FLAT = -2; + + /** + * Creates a new WindowOrientationListener. + * + * @param context for the WindowOrientationListener. + */ + public WindowOrientationListener(Context context) { + this(context, SensorManager.SENSOR_DELAY_NORMAL); + } + + /** + * Creates a new WindowOrientationListener. + * + * @param context for the WindowOrientationListener. + * @param rate at which sensor events are processed (see also + * {@link android.hardware.SensorManager SensorManager}). Use the default + * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL + * SENSOR_DELAY_NORMAL} for simple screen orientation change detection. + */ + public WindowOrientationListener(Context context, int rate) { + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mRate = rate; + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (mSensor != null) { + // Create listener only if sensors do exist + mSensorEventListener = new SensorEventListenerImpl(); + } + } + + /** + * Enables the WindowOrientationListener so it will monitor the sensor and call + * {@link #onOrientationChanged} when the device orientation changes. + */ + public void enable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Not enabled"); + return; + } + if (mEnabled == false) { + if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled"); + mSensorManager.registerListener(mSensorEventListener, mSensor, mRate); + mEnabled = true; + } + } + + /** + * Disables the WindowOrientationListener. + */ + public void disable() { + if (mSensor == null) { + Log.w(TAG, "Cannot detect sensors. Invalid disable"); + return; + } + if (mEnabled == true) { + if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled"); + mSensorManager.unregisterListener(mSensorEventListener); + mEnabled = false; + } + } + + class SensorEventListenerImpl implements SensorEventListener { + private static final int _DATA_X = 0; + private static final int _DATA_Y = 1; + private static final int _DATA_Z = 2; + + public void onSensorChanged(SensorEvent event) { + float[] values = event.values; + int orientation = ORIENTATION_UNKNOWN; + float X = values[_DATA_X]; + float Y = values[_DATA_Y]; + float Z = values[_DATA_Z]; + float OneEightyOverPi = 57.29577957855f; + float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z); + float zyangle = Math.abs((float)Math.asin(Z/gravity)*OneEightyOverPi); + // The device is considered flat if the angle is more than 75 + // if the angle is less than 40, its considered too flat to switch + // orientation. if the angle is between 40 - 75, the orientation is unknown + if (zyangle < 40) { + // Check orientation only if the phone is flat enough + // Don't trust the angle if the magnitude is small compared to the y value + 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; + } + } else if (zyangle >= 75){ + orientation = ORIENTATION_FLAT; + } + + if (orientation != mOrientation) { + mOrientation = orientation; + onOrientationChanged(orientation); + } + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + } + + /* + * Returns true if sensor is enabled and false otherwise + */ + public boolean canDetectOrientation() { + return mSensor != null; + } + + /** + * 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/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..b9c8ec3 --- /dev/null +++ b/core/java/android/view/animation/Animation.java @@ -0,0 +1,925 @@ +/* + * 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; +import android.graphics.RectF; + +/** + * 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 implements Cloneable { + /** + * 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; + + /** + * Indicates whether fillAfter should be taken into account. + */ + boolean mFillEnabled = 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; + + private boolean mMore = true; + private boolean mOneMoreTime = true; + + RectF mPreviousRegion = new RectF(); + RectF mRegion = new RectF(); + Transformation mTransformation = new Transformation(); + Transformation mPreviousTransformation = new Transformation(); + + /** + * 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)); + + setFillEnabled(a.getBoolean(com.android.internal.R.styleable.Animation_fillEnabled, mFillEnabled)); + 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(); + } + + @Override + protected Animation clone() throws CloneNotSupportedException { + final Animation animation = (Animation) super.clone(); + animation.mPreviousRegion = new RectF(); + animation.mRegion = new RectF(); + animation.mTransformation = new Transformation(); + animation.mPreviousTransformation = new Transformation(); + return animation; + } + + /** + * Reset the initialization state of this animation. + * + * @see #initialize(int, int, int, int) + */ + public void reset() { + mPreviousRegion.setEmpty(); + mPreviousTransformation.clear(); + mInitialized = false; + mCycleFlip = false; + mRepeated = 0; + mMore = true; + mOneMoreTime = 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) { + reset(); + mInitialized = 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. The duration cannot be negative. + * + * @param durationMillis Duration in milliseconds + * + * @throw java.lang.IllegalArgumentException if the duration is < 0 + * + * @attr ref android.R.styleable#Animation_duration + */ + public void setDuration(long durationMillis) { + if (durationMillis < 0) { + throw new IllegalArgumentException("Animation duration cannot be negative"); + } + 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 fillEnabled is true, this animation will apply fillBefore and fillAfter. + * + * @return true if the animation will take fillBefore and fillAfter into account + * @attr ref android.R.styleable#Animation_fillEnabled + */ + public boolean isFillEnabled() { + return mFillEnabled; + } + + /** + * If fillEnabled is true, the animation will apply the value of fillBefore and + * fillAfter. Otherwise, fillBefore and fillAfter are ignored and the animation + * transformation is always applied. + * + * @param fillEnabled true if the animation should take fillBefore and fillAfter into account + * @attr ref android.R.styleable#Animation_fillEnabled + * + * @see #setFillBefore(boolean) + * @see #setFillAfter(boolean) + */ + public void setFillEnabled(boolean fillEnabled) { + mFillEnabled = fillEnabled; + } + + /** + * If fillBefore is true, this animation will apply its transformation + * before the start time of the animation. Defaults to true 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 + * + * @see #setFillEnabled(boolean) + */ + 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 + * + * @see #setFillEnabled(boolean) + */ + 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(); + } + } + + /** + * Compute a hint at how long the entire animation may last, in milliseconds. + * Animations can be written to cause themselves to run for a different + * duration than what is computed here, but generally this should be + * accurate. + */ + public long computeDurationHint() { + return (getStartOffset() + getDuration()) * (getRepeatCount() + 1); + } + + /** + * 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(); + final long duration = mDuration; + float normalizedTime; + if (duration != 0) { + normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / + (float) duration; + } else { + // time is a step-change with a zero duration + normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f; + } + + final boolean expired = normalizedTime >= 1.0f; + mMore = !expired; + + if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); + + if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { + if (!mStarted) { + if (mListener != null) { + mListener.onAnimationStart(this); + } + mStarted = true; + } + + if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); + + 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); + } + } + } + + if (!mMore && mOneMoreTime) { + mOneMoreTime = false; + return true; + } + + 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; + } + } + + /** + * @param left + * @param top + * @param right + * @param bottom + * @param invalidate + * @param transformation + * + * @hide + */ + public void getInvalidateRegion(int left, int top, int right, int bottom, + RectF invalidate, Transformation transformation) { + + final RectF tempRegion = mRegion; + final RectF previousRegion = mPreviousRegion; + + invalidate.set(left, top, right, bottom); + transformation.getMatrix().mapRect(invalidate); + tempRegion.set(invalidate); + invalidate.union(previousRegion); + + previousRegion.set(tempRegion); + + final Transformation tempTransformation = mTransformation; + final Transformation previousTransformation = mPreviousTransformation; + + tempTransformation.set(transformation); + transformation.set(previousTransformation); + previousTransformation.set(tempTransformation); + } + + /** + * @param left + * @param top + * @param right + * @param bottom + * + * @hide + */ + public void initializeInvalidateRegion(int left, int top, int right, int bottom) { + final RectF region = mPreviousRegion; + region.set(left, top, right, bottom); + if (mFillBefore) { + final Transformation previousTransformation = mPreviousTransformation; + applyTransformation(0.0f, previousTransformation); + } + } + + /** + * 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 not invoked + * for animations with repeat count set to INFINITE.</p> + * + * @param animation The animation which reached its end. + */ + void onAnimationEnd(Animation animation); + + /** + * <p>Notifies the repetition of the animation.</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..7b56f00 --- /dev/null +++ b/core/java/android/view/animation/AnimationSet.java @@ -0,0 +1,472 @@ +/* + * 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.graphics.RectF; + +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 static final int PROPERTY_CHANGE_BOUNDS_MASK = 0x80; + + 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(); + } + + @Override + protected AnimationSet clone() throws CloneNotSupportedException { + final AnimationSet animation = (AnimationSet) super.clone(); + animation.mTempTransformation = new Transformation(); + animation.mAnimations = new ArrayList<Animation>(); + + final int count = mAnimations.size(); + final ArrayList<Animation> animations = mAnimations; + + for (int i = 0; i < count; i++) { + animation.mAnimations.add(animations.get(i).clone()); + } + + return animation; + } + + 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; + } + + boolean changeBounds = (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == 0; + if (changeBounds && a.willChangeTransformationMatrix()) { + mFlags |= PROPERTY_CHANGE_BOUNDS_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 duration hint of an animation set is the maximum of the duration + * hints of all of its component animations. + * + * @see android.view.animation.Animation#computeDurationHint + */ + public long computeDurationHint() { + long duration = 0; + final int count = mAnimations.size(); + final ArrayList<Animation> animations = mAnimations; + for (int i = count - 1; i >= 0; --i) { + final long d = animations.get(i).computeDurationHint(); + if (d > duration) duration = d; + } + return duration; + } + + /** + * @hide + */ + public void getInvalidateRegion(int left, int top, int right, int bottom, + RectF invalidate, Transformation transformation) { + + final RectF previousRegion = mPreviousRegion; + + invalidate.set(left, top, right, bottom); + transformation.getMatrix().mapRect(invalidate); + invalidate.union(previousRegion); + + previousRegion.set(left, top, right, bottom); + transformation.getMatrix().mapRect(previousRegion); + + final Transformation tempTransformation = mTransformation; + final Transformation previousTransformation = mPreviousTransformation; + + tempTransformation.set(transformation); + transformation.set(previousTransformation); + previousTransformation.set(tempTransformation); + } + + /** + * @hide + */ + public void initializeInvalidateRegion(int left, int top, int right, int bottom) { + final RectF region = mPreviousRegion; + region.set(left, top, right, bottom); + + if (mFillBefore) { + final int count = mAnimations.size(); + final ArrayList<Animation> animations = mAnimations; + final Transformation temp = mTempTransformation; + + final Transformation previousTransformation = mPreviousTransformation; + + for (int i = count - 1; i >= 0; --i) { + final Animation a = animations.get(i); + + temp.clear(); + a.applyTransformation(0.0f, temp); + previousTransformation.compose(temp); + } + } + } + + /** + * 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; + + + long[] storedOffsets = mStoredOffsets; + if (storedOffsets == null || storedOffsets.length != count) { + storedOffsets = mStoredOffsets = new long[count]; + } + + 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) { + long offset = a.getStartOffset(); + a.setStartOffset(offset + startOffset); + storedOffsets[i] = offset; + } + a.initialize(width, height, parentWidth, parentHeight); + } + } + + @Override + public void reset() { + super.reset(); + restoreChildrenStartOffset(); + } + + /** + * @hide + */ + void restoreChildrenStartOffset() { + final long[] offsets = mStoredOffsets; + if (offsets == null) return; + + final ArrayList<Animation> children = mAnimations; + final int count = children.size(); + + + 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; + } + + @Override + public boolean willChangeBounds() { + return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_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..882e738 --- /dev/null +++ b/core/java/android/view/animation/LayoutAnimationController.java @@ -0,0 +1,435 @@ +/* + * 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) + mAnimation.getStartOffset(); + mMaxDelay = Math.max(mMaxDelay, delay); + + try { + final Animation animation = mAnimation.clone(); + animation.setStartOffset(delay); + return animation; + } catch (CloneNotSupportedException e) { + return null; + } + } + + /** + * 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; + } +} 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..f9e85bf --- /dev/null +++ b/core/java/android/view/animation/Transformation.java @@ -0,0 +1,147 @@ +/* + * 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.toShortString() + "}"; + } + + /** + * Return a string representation of the transformation in a compact form. + */ + public String toShortString() { + return "{alpha=" + mAlpha + " matrix=" + mMatrix.toShortString() + "}"; + } +} diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java new file mode 100644 index 0000000..ca936cb --- /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 when a TranslateAnimation 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..87c99bb --- /dev/null +++ b/core/java/android/view/animation/package.html @@ -0,0 +1,20 @@ +<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 (android.view.animation); frame-by-frame animation is + handled by the {@link android.graphics.drawable.AnimationDrawable} class. + </p> + +<p>For more information on creating tweened or frame-by-frame animations, read the discussion in the +<a href="{@docRoot}guide/topics/graphics/2d-graphics.html#tween-animation">2D Graphics</a> +Dev Guide.</p> + +</body> +</html> diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java new file mode 100644 index 0000000..52b4107 --- /dev/null +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -0,0 +1,571 @@ +/* + * 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.inputmethod; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.text.Editable; +import android.text.NoCopySpan; +import android.text.Selection; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.MetaKeyKeyListener; +import android.util.Log; +import android.util.LogPrinter; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewRoot; + +class ComposingText implements NoCopySpan { +} + +/** + * Base class for implementors of the InputConnection interface, taking care + * of most of the common behavior for providing a connection to an Editable. + * Implementors of this class will want to be sure to implement + * {@link #getEditable} to provide access to their own editable object. + */ +public class BaseInputConnection implements InputConnection { + private static final boolean DEBUG = false; + private static final String TAG = "BaseInputConnection"; + static final Object COMPOSING = new ComposingText(); + + final InputMethodManager mIMM; + final Handler mH; + final View mTargetView; + final boolean mDummyMode; + + private Object[] mDefaultComposingSpans; + + Editable mEditable; + KeyCharacterMap mKeyCharacterMap; + + BaseInputConnection(InputMethodManager mgr, boolean dummyMode) { + mIMM = mgr; + mTargetView = null; + mH = null; + mDummyMode = dummyMode; + } + + public BaseInputConnection(View targetView, boolean dummyMode) { + mIMM = (InputMethodManager)targetView.getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); + mH = targetView.getHandler(); + mTargetView = targetView; + mDummyMode = dummyMode; + } + + public static final void removeComposingSpans(Spannable text) { + text.removeSpan(COMPOSING); + Object[] sps = text.getSpans(0, text.length(), Object.class); + if (sps != null) { + for (int i=sps.length-1; i>=0; i--) { + Object o = sps[i]; + if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) { + text.removeSpan(o); + } + } + } + } + + public static void setComposingSpans(Spannable text) { + final Object[] sps = text.getSpans(0, text.length(), Object.class); + if (sps != null) { + for (int i=sps.length-1; i>=0; i--) { + final Object o = sps[i]; + if (o == COMPOSING) { + text.removeSpan(o); + continue; + } + final int fl = text.getSpanFlags(o); + if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) + != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { + text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), + (fl&Spanned.SPAN_POINT_MARK_MASK) + | Spanned.SPAN_COMPOSING + | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + + text.setSpan(COMPOSING, 0, text.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); + } + + public static int getComposingSpanStart(Spannable text) { + return text.getSpanStart(COMPOSING); + } + + public static int getComposingSpanEnd(Spannable text) { + return text.getSpanEnd(COMPOSING); + } + + /** + * Return the target of edit operations. The default implementation + * returns its own fake editable that is just used for composing text; + * subclasses that are real text editors should override this and + * supply their own. + */ + public Editable getEditable() { + if (mEditable == null) { + mEditable = Editable.Factory.getInstance().newEditable(""); + Selection.setSelection(mEditable, 0); + } + return mEditable; + } + + /** + * Default implementation does nothing. + */ + public boolean beginBatchEdit() { + return false; + } + + /** + * Default implementation does nothing. + */ + public boolean endBatchEdit() { + return false; + } + + /** + * Default implementation uses + * {@link MetaKeyKeyListener#clearMetaKeyState(long, int) + * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state. + */ + public boolean clearMetaKeyStates(int states) { + final Editable content = getEditable(); + if (content == null) return false; + MetaKeyKeyListener.clearMetaKeyState(content, states); + return true; + } + + /** + * Default implementation does nothing. + */ + public boolean commitCompletion(CompletionInfo text) { + return false; + } + + /** + * Default implementation replaces any existing composing text with + * the given text. In addition, only if dummy mode, a key event is + * sent for the new text and the current editable buffer cleared. + */ + public boolean commitText(CharSequence text, int newCursorPosition) { + if (DEBUG) Log.v(TAG, "commitText " + text); + replaceText(text, newCursorPosition, false); + sendCurrentText(); + return true; + } + + /** + * The default implementation performs the deletion around the current + * selection position of the editable text. + */ + public boolean deleteSurroundingText(int leftLength, int rightLength) { + if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength + + " / " + rightLength); + final Editable content = getEditable(); + if (content == null) return false; + + beginBatchEdit(); + + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + if (a > b) { + int tmp = a; + a = b; + b = tmp; + } + + // ignore the composing text. + int ca = getComposingSpanStart(content); + int cb = getComposingSpanEnd(content); + if (cb < ca) { + int tmp = ca; + ca = cb; + cb = tmp; + } + if (ca != -1 && cb != -1) { + if (ca < a) a = ca; + if (cb > b) b = cb; + } + + int deleted = 0; + + if (leftLength > 0) { + int start = a - leftLength; + if (start < 0) start = 0; + content.delete(start, a); + deleted = a - start; + } + + if (rightLength > 0) { + b = b - deleted; + + int end = b + rightLength; + if (end > content.length()) end = content.length(); + + content.delete(b, end); + } + + endBatchEdit(); + + return true; + } + + /** + * The default implementation removes the composing state from the + * current editable text. In addition, only if dummy mode, a key event is + * sent for the new text and the current editable buffer cleared. + */ + public boolean finishComposingText() { + if (DEBUG) Log.v(TAG, "finishComposingText"); + final Editable content = getEditable(); + if (content != null) { + beginBatchEdit(); + removeComposingSpans(content); + endBatchEdit(); + sendCurrentText(); + } + return true; + } + + /** + * The default implementation uses TextUtils.getCapsMode to get the + * cursor caps mode for the current selection position in the editable + * text, unless in dummy mode in which case 0 is always returned. + */ + public int getCursorCapsMode(int reqModes) { + if (mDummyMode) return 0; + + final Editable content = getEditable(); + if (content == null) return 0; + + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + if (a > b) { + int tmp = a; + a = b; + b = tmp; + } + + return TextUtils.getCapsMode(content, a, reqModes); + } + + /** + * The default implementation always returns null. + */ + public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { + return null; + } + + /** + * The default implementation returns the given amount of text from the + * current cursor position in the buffer. + */ + public CharSequence getTextBeforeCursor(int length, int flags) { + final Editable content = getEditable(); + if (content == null) return null; + + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + if (a > b) { + int tmp = a; + a = b; + b = tmp; + } + + if (length > a) { + length = a; + } + + if ((flags&GET_TEXT_WITH_STYLES) != 0) { + return content.subSequence(a - length, a); + } + return TextUtils.substring(content, a - length, a); + } + + /** + * The default implementation returns the given amount of text from the + * current cursor position in the buffer. + */ + public CharSequence getTextAfterCursor(int length, int flags) { + final Editable content = getEditable(); + if (content == null) return null; + + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + if (a > b) { + int tmp = a; + a = b; + b = tmp; + } + + if (b + length > content.length()) { + length = content.length() - b; + } + + + if ((flags&GET_TEXT_WITH_STYLES) != 0) { + return content.subSequence(b, b + length); + } + return TextUtils.substring(content, b, b + length); + } + + /** + * The default implementation does nothing. + */ + public boolean performEditorAction(int actionCode) { + return false; + } + + /** + * The default implementation does nothing. + */ + public boolean performContextMenuAction(int id) { + return false; + } + + /** + * The default implementation does nothing. + */ + public boolean performPrivateCommand(String action, Bundle data) { + return false; + } + + /** + * The default implementation places the given text into the editable, + * replacing any existing composing text. The new text is marked as + * in a composing state with the composing style. + */ + public boolean setComposingText(CharSequence text, int newCursorPosition) { + if (DEBUG) Log.v(TAG, "setComposingText " + text); + replaceText(text, newCursorPosition, true); + return true; + } + + /** + * The default implementation changes the selection position in the + * current editable text. + */ + public boolean setSelection(int start, int end) { + if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end); + final Editable content = getEditable(); + if (content == null) return false; + int len = content.length(); + if (start > len || end > len) { + // If the given selection is out of bounds, just ignore it. + // Most likely the text was changed out from under the IME, + // the the IME is going to have to update all of its state + // anyway. + return true; + } + Selection.setSelection(content, start, end); + return true; + } + + /** + * Provides standard implementation for sending a key event to the window + * attached to the input connection's view. + */ + public boolean sendKeyEvent(KeyEvent event) { + synchronized (mIMM.mH) { + Handler h = mH; + if (h == null) { + if (mIMM.mServedView != null) { + h = mIMM.mServedView.getHandler(); + } + } + if (h != null) { + h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, + event)); + } + } + return false; + } + + /** + * Updates InputMethodManager with the current fullscreen mode. + */ + public boolean reportFullscreenMode(boolean enabled) { + mIMM.setFullscreenMode(enabled); + return true; + } + + private void sendCurrentText() { + if (!mDummyMode) { + return; + } + + Editable content = getEditable(); + if (content != null) { + final int N = content.length(); + if (N == 0) { + return; + } + if (N == 1) { + // If it's 1 character, we have a chance of being + // able to generate normal key events... + if (mKeyCharacterMap == null) { + mKeyCharacterMap = KeyCharacterMap.load( + KeyCharacterMap.BUILT_IN_KEYBOARD); + } + char[] chars = new char[1]; + content.getChars(0, 1, chars, 0); + KeyEvent[] events = mKeyCharacterMap.getEvents(chars); + if (events != null) { + for (int i=0; i<events.length; i++) { + if (DEBUG) Log.v(TAG, "Sending: " + events[i]); + sendKeyEvent(events[i]); + } + content.clear(); + return; + } + } + + // Otherwise, revert to the special key event containing + // the actual characters. + KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), + content.toString(), KeyCharacterMap.BUILT_IN_KEYBOARD, 0); + sendKeyEvent(event); + content.clear(); + } + } + + private void replaceText(CharSequence text, int newCursorPosition, + boolean composing) { + final Editable content = getEditable(); + if (content == null) { + return; + } + + beginBatchEdit(); + + // delete composing text set previously. + int a = getComposingSpanStart(content); + int b = getComposingSpanEnd(content); + + if (DEBUG) Log.v(TAG, "Composing span: " + a + " to " + b); + + if (b < a) { + int tmp = a; + a = b; + b = tmp; + } + + if (a != -1 && b != -1) { + removeComposingSpans(content); + } else { + a = Selection.getSelectionStart(content); + b = Selection.getSelectionEnd(content); + if (a >=0 && b>= 0 && a != b) { + if (b < a) { + int tmp = a; + a = b; + b = tmp; + } + } + } + + if (composing) { + Spannable sp = null; + if (!(text instanceof Spannable)) { + sp = new SpannableStringBuilder(text); + text = sp; + if (mDefaultComposingSpans == null) { + Context context; + if (mTargetView != null) { + context = mTargetView.getContext(); + } else if (mIMM.mServedView != null) { + context = mIMM.mServedView.getContext(); + } else { + context = null; + } + if (context != null) { + TypedArray ta = context.getTheme() + .obtainStyledAttributes(new int[] { + com.android.internal.R.attr.candidatesTextStyleSpans + }); + CharSequence style = ta.getText(0); + ta.recycle(); + if (style != null && style instanceof Spanned) { + mDefaultComposingSpans = ((Spanned)style).getSpans( + 0, style.length(), Object.class); + } + } + } + if (mDefaultComposingSpans != null) { + for (int i = 0; i < mDefaultComposingSpans.length; ++i) { + sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } else { + sp = (Spannable)text; + } + setComposingSpans(sp); + } + + if (DEBUG) Log.v(TAG, "Replacing from " + a + " to " + b + " with \"" + + text + "\", composing=" + composing + + ", type=" + text.getClass().getCanonicalName()); + + if (DEBUG) { + LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); + lp.println("Current text:"); + TextUtils.dumpSpans(content, lp, " "); + lp.println("Composing text:"); + TextUtils.dumpSpans(text, lp, " "); + } + + // Position the cursor appropriately, so that after replacing the + // desired range of text it will be located in the correct spot. + // This allows us to deal with filters performing edits on the text + // we are providing here. + if (newCursorPosition > 0) { + newCursorPosition += b - 1; + } else { + newCursorPosition += a; + } + if (newCursorPosition < 0) newCursorPosition = 0; + if (newCursorPosition > content.length()) + newCursorPosition = content.length(); + Selection.setSelection(content, newCursorPosition); + + content.replace(a, b, text); + + if (DEBUG) { + LogPrinter lp = new LogPrinter(Log.VERBOSE, TAG); + lp.println("Final text:"); + TextUtils.dumpSpans(content, lp, " "); + } + + endBatchEdit(); + } +} diff --git a/core/java/android/view/inputmethod/CompletionInfo.aidl b/core/java/android/view/inputmethod/CompletionInfo.aidl new file mode 100644 index 0000000..e601054 --- /dev/null +++ b/core/java/android/view/inputmethod/CompletionInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.inputmethod; + +parcelable CompletionInfo; diff --git a/core/java/android/view/inputmethod/CompletionInfo.java b/core/java/android/view/inputmethod/CompletionInfo.java new file mode 100644 index 0000000..3a8fe72 --- /dev/null +++ b/core/java/android/view/inputmethod/CompletionInfo.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2007-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.inputmethod; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * Information about a single text completion that an editor has reported to + * an input method. + */ +public final class CompletionInfo implements Parcelable { + static final String TAG = "CompletionInfo"; + + final long mId; + final int mPosition; + final CharSequence mText; + final CharSequence mLabel; + + /** + * Create a simple completion with just text, no label. + */ + public CompletionInfo(long id, int index, CharSequence text) { + mId = id; + mPosition = index; + mText = text; + mLabel = null; + } + + /** + * Create a full completion with both text and label. + */ + public CompletionInfo(long id, int index, CharSequence text, CharSequence label) { + mId = id; + mPosition = index; + mText = text; + mLabel = label; + } + + CompletionInfo(Parcel source) { + mId = source.readLong(); + mPosition = source.readInt(); + mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + } + + /** + * Return the abstract identifier for this completion, typically + * corresponding to the id associated with it in the original adapter. + */ + public long getId() { + return mId; + } + + /** + * Return the original position of this completion, typically + * corresponding to its position in the original adapter. + */ + public int getPosition() { + return mPosition; + } + + /** + * Return the actual text associated with this completion. This is the + * real text that will be inserted into the editor if the user selects it. + */ + public CharSequence getText() { + return mText; + } + + /** + * Return the user-visible label for the completion, or null if the plain + * text should be shown. If non-null, this will be what the user sees as + * the completion option instead of the actual text. + */ + public CharSequence getLabel() { + return mLabel; + } + + @Override + public String toString() { + return "CompletionInfo{#" + mPosition + " \"" + mText + + "\" id=" + mId + " label=" + mLabel + "}"; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mId); + dest.writeInt(mPosition); + TextUtils.writeToParcel(mText, dest, flags); + TextUtils.writeToParcel(mLabel, dest, flags); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<CompletionInfo> CREATOR + = new Parcelable.Creator<CompletionInfo>() { + public CompletionInfo createFromParcel(Parcel source) { + return new CompletionInfo(source); + } + + public CompletionInfo[] newArray(int size) { + return new CompletionInfo[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/inputmethod/EditorInfo.aidl b/core/java/android/view/inputmethod/EditorInfo.aidl new file mode 100644 index 0000000..48068f0 --- /dev/null +++ b/core/java/android/view/inputmethod/EditorInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.inputmethod; + +parcelable EditorInfo; diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java new file mode 100644 index 0000000..0405371 --- /dev/null +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -0,0 +1,263 @@ +package android.view.inputmethod; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.InputType; +import android.text.TextUtils; +import android.util.Printer; + +/** + * An EditorInfo describes several attributes of a text editing object + * that an input method is communicating with (typically an EditText), most + * importantly the type of text content it contains. + */ +public class EditorInfo implements InputType, Parcelable { + /** + * The content type of the text box, whose bits are defined by + * {@link InputType}. + * + * @see InputType + * @see #TYPE_MASK_CLASS + * @see #TYPE_MASK_VARIATION + * @see #TYPE_MASK_FLAGS + */ + public int inputType = TYPE_NULL; + + /** + * Set of bits in {@link #imeOptions} that provide alternative actions + * associated with the "enter" key. This both helps the IME provide + * better feedback about what the enter key will do, and also allows it + * to provide alternative mechanisms for providing that command. + */ + public static final int IME_MASK_ACTION = 0x000000ff; + + /** + * Bits of {@link #IME_MASK_ACTION}: there is no special action + * associated with this editor. + */ + public static final int IME_ACTION_NONE = 0x00000000; + + /** + * Bits of {@link #IME_MASK_ACTION}: the action key performs a "go" + * operation to take the user to the target of the text they typed. + * Typically used, for example, when entering a URL. + */ + public static final int IME_ACTION_GO = 0x00000001; + + /** + * Bits of {@link #IME_MASK_ACTION}: the action key performs a "search" + * operation, taking the user to the results of searching for the text + * the have typed (in whatever context is appropriate). + */ + public static final int IME_ACTION_SEARCH = 0x00000002; + + /** + * Bits of {@link #IME_MASK_ACTION}: the action key performs a "send" + * operation, delivering the text to its target. This is typically used + * when composing a message. + */ + public static final int IME_ACTION_SEND = 0x00000003; + + /** + * Bits of {@link #IME_MASK_ACTION}: the action key performs a "next" + * operation, taking the user to the next field that will accept text. + */ + public static final int IME_ACTION_NEXT = 0x00000004; + + /** + * Flag of {@link #imeOptions}: used in conjunction with + * {@link #IME_MASK_ACTION}, this indicates that the action should not + * be available in-line as the same as a "enter" key. Typically this is + * because the action has such a significant impact or is not recoverable + * enough that accidentally hitting it should be avoided, such as sending + * a message. + */ + public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000; + + /** + * Generic non-special type for {@link #imeOptions}. + */ + public static final int IME_NORMAL = 0x00000000; + + /** + * Special code for when the ime option has been undefined. This is not + * used with the EditorInfo structure, but can be used elsewhere. + */ + public static final int IME_UNDEFINED = 0x80000000; + + /** + * Extended type information for the editor, to help the IME better + * integrate with it. + */ + public int imeOptions = IME_NORMAL; + + /** + * A string supplying additional information options that are + * private to a particular IME implementation. The string must be + * scoped to a package owned by the implementation, to ensure there are + * no conflicts between implementations, but other than that you can put + * whatever you want in it to communicate with the IME. For example, + * you could have a string that supplies an argument like + * <code>"com.example.myapp.SpecialMode=3"</code>. This field is can be + * filled in from the {@link android.R.attr#privateImeOptions} + * attribute of a TextView. + */ + public String privateImeOptions = null; + + /** + * In some cases an IME may be able to display an arbitrary label for + * a command the user can perform, which you can specify here. You can + * not count on this being used. + */ + public CharSequence actionLabel = null; + + /** + * If {@link #actionLabel} has been given, this is the id for that command + * when the user presses its button that is delivered back with + * {@link InputConnection#performEditorAction(int) + * InputConnection.performEditorAction()}. + */ + public int actionId = 0; + + /** + * The text offset of the start of the selection at the time editing + * began; -1 if not known. + */ + public int initialSelStart = -1; + + /** + * The text offset of the end of the selection at the time editing + * began; -1 if not known. + */ + public int initialSelEnd = -1; + + /** + * The capitalization mode of the first character being edited in the + * text. Values may be any combination of + * {@link TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS}, + * {@link TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}, and + * {@link TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}, though + * you should generally just take a non-zero value to mean start out in + * caps mode. + */ + public int initialCapsMode = 0; + + /** + * The "hint" text of the text view, typically shown in-line when the + * text is empty to tell the user what to enter. + */ + public CharSequence hintText; + + /** + * A label to show to the user describing the text they are writing. + */ + public CharSequence label; + + /** + * Name of the package that owns this editor. + */ + public String packageName; + + /** + * Identifier for the editor's field. This is optional, and may be + * 0. By default it is filled in with the result of + * {@link android.view.View#getId() View.getId()} on the View that + * is being edited. + */ + public int fieldId; + + /** + * Additional name for the editor's field. This can supply additional + * name information for the field. By default it is null. The actual + * contents have no meaning. + */ + public String fieldName; + + /** + * Any extra data to supply to the input method. This is for extended + * communication with specific input methods; the name fields in the + * bundle should be scoped (such as "com.mydomain.im.SOME_FIELD") so + * that they don't conflict with others. This field is can be + * filled in from the {@link android.R.attr#editorExtras} + * attribute of a TextView. + */ + public Bundle extras; + + /** + * Write debug output of this object. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "inputType=0x" + Integer.toHexString(inputType) + + " imeOptions=0x" + Integer.toHexString(imeOptions) + + " privateImeOptions=" + privateImeOptions); + pw.println(prefix + "actionLabel=" + actionLabel + + " actionId=" + actionId); + pw.println(prefix + "initialSelStart=" + initialSelStart + + " initialSelEnd=" + initialSelEnd + + " initialCapsMode=0x" + + Integer.toHexString(initialCapsMode)); + pw.println(prefix + "hintText=" + hintText + + " label=" + label); + pw.println(prefix + "packageName=" + packageName + + " fieldId=" + fieldId + + " fieldName=" + fieldName); + pw.println(prefix + "extras=" + extras); + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(inputType); + dest.writeInt(imeOptions); + dest.writeString(privateImeOptions); + TextUtils.writeToParcel(actionLabel, dest, flags); + dest.writeInt(actionId); + dest.writeInt(initialSelStart); + dest.writeInt(initialSelEnd); + dest.writeInt(initialCapsMode); + TextUtils.writeToParcel(hintText, dest, flags); + TextUtils.writeToParcel(label, dest, flags); + dest.writeString(packageName); + dest.writeInt(fieldId); + dest.writeString(fieldName); + dest.writeBundle(extras); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<EditorInfo> CREATOR = new Parcelable.Creator<EditorInfo>() { + public EditorInfo createFromParcel(Parcel source) { + EditorInfo res = new EditorInfo(); + res.inputType = source.readInt(); + res.imeOptions = source.readInt(); + res.privateImeOptions = source.readString(); + res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + res.actionId = source.readInt(); + res.initialSelStart = source.readInt(); + res.initialSelEnd = source.readInt(); + res.initialCapsMode = source.readInt(); + res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + res.packageName = source.readString(); + res.fieldId = source.readInt(); + res.fieldName = source.readString(); + res.extras = source.readBundle(); + return res; + } + + public EditorInfo[] newArray(int size) { + return new EditorInfo[size]; + } + }; + + public int describeContents() { + return 0; + } + +} diff --git a/core/java/android/view/inputmethod/ExtractedText.aidl b/core/java/android/view/inputmethod/ExtractedText.aidl new file mode 100644 index 0000000..95e56d7 --- /dev/null +++ b/core/java/android/view/inputmethod/ExtractedText.aidl @@ -0,0 +1,19 @@ +/* + * 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.inputmethod; + +parcelable ExtractedText; diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java new file mode 100644 index 0000000..e5d3cae --- /dev/null +++ b/core/java/android/view/inputmethod/ExtractedText.java @@ -0,0 +1,102 @@ +package android.view.inputmethod; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * Information about text that has been extracted for use by an input method. + */ +public class ExtractedText implements Parcelable { + /** + * The text that has been extracted. + */ + public CharSequence text; + + /** + * The offset in the overall text at which the extracted text starts. + */ + public int startOffset; + + /** + * If the content is a report of a partial text change, this is the + * offset where the change starts and it runs until + * {@link #partialEndOffset}. If the content is the full text, this + * field is -1. + */ + public int partialStartOffset; + + /** + * If the content is a report of a partial text change, this is the offset + * where the change ends. Note that the actual text may be larger or + * smaller than the difference between this and {@link #partialEndOffset}, + * meaning a reduction or increase, respectively, in the total text. + */ + public int partialEndOffset; + + /** + * The offset where the selection currently starts within the extracted + * text. The real selection start position is at + * <var>startOffset</var>+<var>selectionStart</var>. + */ + public int selectionStart; + + /** + * The offset where the selection currently ends within the extracted + * text. The real selection end position is at + * <var>startOffset</var>+<var>selectionEnd</var>. + */ + public int selectionEnd; + + /** + * Bit for {@link #flags}: set if the text being edited can only be on + * a single line. + */ + public static final int FLAG_SINGLE_LINE = 0x0001; + + /** + * Additional bit flags of information about the edited text. + */ + public int flags; + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + TextUtils.writeToParcel(text, dest, flags); + dest.writeInt(startOffset); + dest.writeInt(partialStartOffset); + dest.writeInt(partialEndOffset); + dest.writeInt(selectionStart); + dest.writeInt(selectionEnd); + dest.writeInt(flags); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<ExtractedText> CREATOR = new Parcelable.Creator<ExtractedText>() { + public ExtractedText createFromParcel(Parcel source) { + ExtractedText res = new ExtractedText(); + res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + res.startOffset = source.readInt(); + res.partialStartOffset = source.readInt(); + res.partialEndOffset = source.readInt(); + res.selectionStart = source.readInt(); + res.selectionEnd = source.readInt(); + res.flags = source.readInt(); + return res; + } + + public ExtractedText[] newArray(int size) { + return new ExtractedText[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.aidl b/core/java/android/view/inputmethod/ExtractedTextRequest.aidl new file mode 100644 index 0000000..c69acc7 --- /dev/null +++ b/core/java/android/view/inputmethod/ExtractedTextRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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.inputmethod; + +parcelable ExtractedTextRequest; diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.java b/core/java/android/view/inputmethod/ExtractedTextRequest.java new file mode 100644 index 0000000..e84b094 --- /dev/null +++ b/core/java/android/view/inputmethod/ExtractedTextRequest.java @@ -0,0 +1,70 @@ +package android.view.inputmethod; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * Description of what an input method would like from an application when + * extract text from its input editor. + */ +public class ExtractedTextRequest implements Parcelable { + /** + * Arbitrary integer that can be supplied in the request, which will be + * delivered back when reporting updates. + */ + public int token; + + /** + * Additional request flags, having the same possible values as the + * flags parameter of {@link InputConnection#getTextBeforeCursor + * InputConnection.getTextBeforeCursor()}. + */ + public int flags; + + /** + * Hint for the maximum number of lines to return. + */ + public int hintMaxLines; + + /** + * Hint for the maximum number of characters to return. + */ + public int hintMaxChars; + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(token); + dest.writeInt(this.flags); + dest.writeInt(hintMaxLines); + dest.writeInt(hintMaxChars); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<ExtractedTextRequest> CREATOR + = new Parcelable.Creator<ExtractedTextRequest>() { + public ExtractedTextRequest createFromParcel(Parcel source) { + ExtractedTextRequest res = new ExtractedTextRequest(); + res.token = source.readInt(); + res.flags = source.readInt(); + res.hintMaxLines = source.readInt(); + res.hintMaxChars = source.readInt(); + return res; + } + + public ExtractedTextRequest[] newArray(int size) { + return new ExtractedTextRequest[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/inputmethod/InputBinding.aidl b/core/java/android/view/inputmethod/InputBinding.aidl new file mode 100644 index 0000000..ea09d8b --- /dev/null +++ b/core/java/android/view/inputmethod/InputBinding.aidl @@ -0,0 +1,19 @@ +/* + * 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.inputmethod; + +parcelable InputBinding; diff --git a/core/java/android/view/inputmethod/InputBinding.java b/core/java/android/view/inputmethod/InputBinding.java new file mode 100644 index 0000000..f4209ef --- /dev/null +++ b/core/java/android/view/inputmethod/InputBinding.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2007-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.inputmethod; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * Information given to an {@link InputMethod} about a client connecting + * to it. + */ +public final class InputBinding implements Parcelable { + static final String TAG = "InputBinding"; + + /** + * The connection back to the client. + */ + final InputConnection mConnection; + + /** + * A remotable token for the connection back to the client. + */ + final IBinder mConnectionToken; + + /** + * The UID where this binding came from. + */ + final int mUid; + + /** + * The PID where this binding came from. + */ + final int mPid; + + /** + * Constructor. + * + * @param conn The interface for communicating back with the application. + * @param connToken A remoteable token for communicating across processes. + * @param uid The user id of the client of this binding. + * @param pid The process id of where the binding came from. + */ + public InputBinding(InputConnection conn, IBinder connToken, + int uid, int pid) { + mConnection = conn; + mConnectionToken = connToken; + mUid = uid; + mPid = pid; + } + + /** + * Constructor from an existing InputBinding taking a new local input + * connection interface. + * + * @param conn The new connection interface. + * @param binding Existing binding to copy. + */ + public InputBinding(InputConnection conn, InputBinding binding) { + mConnection = conn; + mConnectionToken = binding.getConnectionToken(); + mUid = binding.getUid(); + mPid = binding.getPid(); + } + + InputBinding(Parcel source) { + mConnection = null; + mConnectionToken = source.readStrongBinder(); + mUid = source.readInt(); + mPid = source.readInt(); + } + + /** + * Return the connection for interacting back with the application. + */ + public InputConnection getConnection() { + return mConnection; + } + + /** + * Return the token for the connection back to the application. You can + * not use this directly, it must be converted to a {@link InputConnection} + * for you. + */ + public IBinder getConnectionToken() { + return mConnectionToken; + } + + /** + * Return the user id of the client associated with this binding. + */ + public int getUid() { + return mUid; + } + + /** + * Return the process id where this binding came from. + */ + public int getPid() { + return mPid; + } + + @Override + public String toString() { + return "InputBinding{" + mConnectionToken + + " / uid " + mUid + " / pid " + mPid + "}"; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mConnectionToken); + dest.writeInt(mUid); + dest.writeInt(mPid); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<InputBinding> CREATOR = new Parcelable.Creator<InputBinding>() { + public InputBinding createFromParcel(Parcel source) { + return new InputBinding(source); + } + + public InputBinding[] newArray(int size) { + return new InputBinding[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java new file mode 100644 index 0000000..32cce35 --- /dev/null +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2007-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.inputmethod; + +import android.os.Bundle; +import android.text.Spanned; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; + +/** + * The InputConnection interface is the communication channel from an + * {@link InputMethod} back to the application that is receiving its input. It + * is used to perform such things as reading text around the cursor, + * committing text to the text box, and sending raw key events to the application. + * + * <p>Implementations of this interface should generally be done by + * subclassing {@link BaseInputConnection}. + */ +public interface InputConnection { + /** + * Flag for use with {@link #getTextAfterCursor} and + * {@link #getTextBeforeCursor} to have style information returned along + * with the text. If not set, you will receive only the raw text. If + * set, you may receive a complex CharSequence of both text and style + * spans. + */ + static final int GET_TEXT_WITH_STYLES = 0x0001; + + /** + * Flag for use with {@link #getExtractedText} to indicate you would + * like to receive updates when the extracted text changes. + */ + public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001; + + /** + * Get <var>n</var> characters of text before the current cursor position. + * + * <p>This method may fail either if the input connection has become invalid + * (such as its process crashing) or the client is taking too long to + * respond with the text (it is given a couple seconds to return). + * In either case, a null is returned. + * + * @param n The expected length of the text. + * @param flags Supplies additional options controlling how the text is + * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}. + * + * @return Returns the text before the cursor position; the length of the + * returned text might be less than <var>n</var>. + */ + public CharSequence getTextBeforeCursor(int n, int flags); + + /** + * Get <var>n</var> characters of text after the current cursor position. + * + * <p>This method may fail either if the input connection has become invalid + * (such as its process crashing) or the client is taking too long to + * respond with the text (it is given a couple seconds to return). + * In either case, a null is returned. + * + * @param n The expected length of the text. + * @param flags Supplies additional options controlling how the text is + * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}. + * + * @return Returns the text after the cursor position; the length of the + * returned text might be less than <var>n</var>. + */ + public CharSequence getTextAfterCursor(int n, int flags); + + /** + * Retrieve the current capitalization mode in effect at the current + * cursor position in the text. See + * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode} for + * more information. + * + * <p>This method may fail either if the input connection has become invalid + * (such as its process crashing) or the client is taking too long to + * respond with the text (it is given a couple seconds to return). + * In either case, a 0 is returned. + * + * @param reqModes The desired modes to retrieve, as defined by + * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These + * constants are defined so that you can simply pass the current + * {@link EditorInfo#inputType TextBoxAttribute.contentType} value + * directly in to here. + * + * @return Returns the caps mode flags that are in effect. + */ + public int getCursorCapsMode(int reqModes); + + /** + * Retrieve the current text in the input connection's editor, and monitor + * for any changes to it. This function returns with the current text, + * and optionally the input connection can send updates to the + * input method when its text changes. + * + * <p>This method may fail either if the input connection has become invalid + * (such as its process crashing) or the client is taking too long to + * respond with the text (it is given a couple seconds to return). + * In either case, a null is returned. + * + * @param request Description of how the text should be returned. + * @param flags Additional options to control the client, either 0 or + * {@link #GET_EXTRACTED_TEXT_MONITOR}. + * + * @return Returns an ExtractedText object describing the state of the + * text view and containing the extracted text itself. + */ + public ExtractedText getExtractedText(ExtractedTextRequest request, + int flags); + + /** + * Delete <var>leftLength</var> characters of text before the current cursor + * position, and delete <var>rightLength</var> characters of text after the + * current cursor position, excluding composing text. + * + * @param leftLength The number of characters to be deleted before the + * current cursor position. + * @param rightLength The number of characters to be deleted after the + * current cursor position. + * + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + boolean deleteSurroundingText(int leftLength, int rightLength); + + /** + * Set composing text around the current cursor position with the given text, + * and set the new cursor position. Any composing text set previously will + * be removed automatically. + * + * @param text The composing text with styles if necessary. If no style + * object attached to the text, the default style for composing text + * is used. See {#link android.text.Spanned} for how to attach style + * object to the text. {#link android.text.SpannableString} and + * {#link android.text.SpannableStringBuilder} are two + * implementations of the interface {#link android.text.Spanned}. + * @param newCursorPosition The new cursor position around the text. If + * > 0, this is relative to the end of the text - 1; if <= 0, this + * is relative to the start of the text. So a value of 1 will + * always advance you to the position after the full text being + * inserted. Note that this means you can't position the cursor + * within the text, because the editor can make modifications to + * the text you are providing so it is not possible to correctly + * specify locations there. + * + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + public boolean setComposingText(CharSequence text, int newCursorPosition); + + /** + * Have the text editor finish whatever composing text is currently + * active. This simply leaves the text as-is, removing any special + * composing styling or other state that was around it. The cursor + * position remains unchanged. + */ + public boolean finishComposingText(); + + /** + * Commit text to the text box and set the new cursor position. + * Any composing text set previously will be removed + * automatically. + * + * @param text The committed text. + * @param newCursorPosition The new cursor position around the text. If + * > 0, this is relative to the end of the text - 1; if <= 0, this + * is relative to the start of the text. So a value of 1 will + * always advance you to the position after the full text being + * inserted. Note that this means you can't position the cursor + * within the text, because the editor can make modifications to + * the text you are providing so it is not possible to correctly + * specify locations there. + * + * + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + public boolean commitText(CharSequence text, int newCursorPosition); + + /** + * Commit a completion the user has selected from the possible ones + * previously reported to {@link InputMethodSession#displayCompletions + * InputMethodSession.displayCompletions()}. This will result in the + * same behavior as if the user had selected the completion from the + * actual UI. + * + * @param text The committed completion. + * + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + public boolean commitCompletion(CompletionInfo text); + + /** + * Set the selection of the text editor. To set the cursor position, + * start and end should have the same value. + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + public boolean setSelection(int start, int end); + + /** + * Have the editor perform an action it has said it can do. + * + * @param editorAction This must be one of the action constants for + * {@link EditorInfo#imeOptions EditorInfo.editorType}, such as + * {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}. + * + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + public boolean performEditorAction(int editorAction); + + /** + * Perform a context menu action on the field. The given id may be one of: + * {@link android.R.id#selectAll}, + * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText}, + * {@link android.R.id#cut}, {@link android.R.id#copy}, + * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, + * or {@link android.R.id#switchInputMethod} + */ + public boolean performContextMenuAction(int id); + + /** + * Tell the editor that you are starting a batch of editor operations. + * The editor will try to avoid sending you updates about its state + * until {@link #endBatchEdit} is called. + */ + public boolean beginBatchEdit(); + + /** + * Tell the editor that you are done with a batch edit previously + * initiated with {@link #endBatchEdit}. + */ + public boolean endBatchEdit(); + + /** + * Send a key event to the process that is currently attached through + * this input connection. The event will be dispatched like a normal + * key event, to the currently focused; this generally is the view that + * is providing this InputConnection, but due to the asynchronous nature + * of this protocol that can not be guaranteed and the focus may have + * changed by the time the event is received. + * + * <p> + * This method can be used to send key events to the application. For + * example, an on-screen keyboard may use this method to simulate a hardware + * keyboard. There are three types of standard keyboards, numeric (12-key), + * predictive (20-key) and ALPHA (QWERTY). You can specify the keyboard type + * by specify the device id of the key event. + * + * <p> + * You will usually want to set the flag + * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD} on all + * key event objects you give to this API; the flag will not be set + * for you. + * + * @param event The key event. + * + * @return Returns true on success, false if the input connection is no longer + * valid. + * + * @see KeyEvent + * @see KeyCharacterMap#NUMERIC + * @see KeyCharacterMap#PREDICTIVE + * @see KeyCharacterMap#ALPHA + */ + public boolean sendKeyEvent(KeyEvent event); + + /** + * Clear the given meta key pressed states in the given input connection. + * + * @param states The states to be cleared, may be one or more bits as + * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}. + * + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + public boolean clearMetaKeyStates(int states); + + /** + * Called by the IME to tell the client when it switches between fullscreen + * and normal modes. This will normally be called for you by the standard + * implementation of {@link android.inputmethodservice.InputMethodService}. + */ + public boolean reportFullscreenMode(boolean enabled); + + /** + * API to send private commands from an input method to its connected + * editor. This can be used to provide domain-specific features that are + * only known between certain input methods and their clients. Note that + * because the InputConnection protocol is asynchronous, you have no way + * to get a result back or know if the client understood the command; you + * can use the information in {@link EditorInfo} to determine if + * a client supports a particular command. + * + * @param action Name of the command to be performed. This <em>must</em> + * be a scoped name, i.e. prefixed with a package name you own, so that + * different developers will not create conflicting commands. + * @param data Any data to include with the command. + * @return Returns true if the command was sent (whether or not the + * associated editor understood it), false if the input connection is no longer + * valid. + */ + public boolean performPrivateCommand(String action, Bundle data); +} diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java new file mode 100644 index 0000000..740dca8 --- /dev/null +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2007-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.inputmethod; + +import android.inputmethodservice.InputMethodService; +import android.os.IBinder; + +/** + * The InputMethod interface represents an input method which can generate key + * events and text, such as digital, email addresses, CJK characters, other + * language characters, and etc., while handling various input events, and send + * the text back to the application that requests text input. See + * {@link InputMethodManager} for more general information about the + * architecture. + * + * <p>Applications will not normally use this interface themselves, instead + * relying on the standard interaction provided by + * {@link android.widget.TextView} and {@link android.widget.EditText}. + * + * <p>Those implementing input methods should normally do so by deriving from + * {@link InputMethodService} or one of its subclasses. When implementing + * an input method, the service component containing it must also supply + * a {@link #SERVICE_META_DATA} meta-data field, referencing an XML resource + * providing details about the input method. All input methods also must + * require that clients hold the + * {@link android.Manifest.permission#BIND_INPUT_METHOD} in order to interact + * with the service; if this is not required, the system will not use that + * input method, because it can not trust that it is not compromised. + * + * <p>The InputMethod interface is actually split into two parts: the interface + * here is the top-level interface to the input method, providing all + * access to it, which only the system can access (due to the BIND_INPUT_METHOD + * permission requirement). In addition its method + * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)} + * can be called to instantate a secondary {@link InputMethodSession} interface + * which is what clients use to communicate with the input method. + */ +public interface InputMethod { + /** + * This is the interface name that a service implementing an input + * method should say that it supports -- that is, this is the action it + * uses for its intent filter. (Note: this name is used because this + * interface should be moved to the view package.) + */ + public static final String SERVICE_INTERFACE = "android.view.InputMethod"; + + /** + * Name under which an InputMethod service component publishes information + * about itself. This meta-data must reference an XML resource containing + * an + * <code><{@link android.R.styleable#InputMethod input-method}></code> + * tag. + */ + public static final String SERVICE_META_DATA = "android.view.im"; + + public interface SessionCallback { + public void sessionCreated(InputMethodSession session); + } + + /** + * Called first thing after an input method is created, this supplies a + * unique token for the session it has with the system service. It is + * needed to identify itself with the service to validate its operations. + * This token <strong>must not</strong> be passed to applications, since + * it grants special priviledges that should not be given to applications. + * + * <p>Note: to protect yourself from malicious clients, you should only + * accept the first token given to you. Any after that may come from the + * client. + */ + public void attachToken(IBinder token); + + /** + * Bind a new application environment in to the input method, so that it + * can later start and stop input processing. + * Typically this method is called when this input method is enabled in an + * application for the first time. + * + * @param binding Information about the application window that is binding + * to the input method. + * + * @see InputBinding + * @see #unbindInput() + */ + public void bindInput(InputBinding binding); + + /** + * Unbind an application environment, called when the information previously + * set by {@link #bindInput} is no longer valid for this input method. + * + * <p> + * Typically this method is called when the application changes to be + * non-foreground. + */ + public void unbindInput(); + + /** + * This method is called when the application starts to receive text and it + * is ready for this input method to process received events and send result + * text back to the application. + * + * @param inputConnection Optional specific input connection for + * communicating with the text box; if null, you should use the generic + * bound input connection. + * @param info Information about the text box (typically, an EditText) + * that requests input. + * + * @see EditorInfo + */ + public void startInput(InputConnection inputConnection, EditorInfo info); + + /** + * This method is called when the state of this input method needs to be + * reset. + * + * <p> + * Typically, this method is called when the input focus is moved from one + * text box to another. + * + * @param inputConnection Optional specific input connection for + * communicating with the text box; if null, you should use the generic + * bound input connection. + * @param attribute The attribute of the text box (typically, a EditText) + * that requests input. + * + * @see EditorInfo + */ + public void restartInput(InputConnection inputConnection, EditorInfo attribute); + + /** + * Create a new {@link InputMethodSession} that can be handed to client + * applications for interacting with the input method. You can later + * use {@link #revokeSession(InputMethodSession)} to destroy the session + * so that it can no longer be used by any clients. + * + * @param callback Interface that is called with the newly created session. + */ + public void createSession(SessionCallback callback); + + /** + * Control whether a particular input method session is active. + * + * @param session The {@link InputMethodSession} previously provided through + * SessionCallback.sessionCreated() that is to be changed. + */ + public void setSessionEnabled(InputMethodSession session, boolean enabled); + + /** + * Disable and destroy a session that was previously created with + * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}. + * After this call, the given session interface is no longer active and + * calls on it will fail. + * + * @param session The {@link InputMethodSession} previously provided through + * SessionCallback.sessionCreated() that is to be revoked. + */ + public void revokeSession(InputMethodSession session); + + /** + * Flag for {@link #showSoftInput(int)}: this show has been explicitly + * requested by the user. If not set, the system has decided it may be + * a good idea to show the input method based on a navigation operation + * in the UI. + */ + public static final int SHOW_EXPLICIT = 0x00001; + + /** + * Flag for {@link #showSoftInput(int)}: this show has been forced to + * happen by the user. If set, the input method should remain visible + * until deliberated dismissed by the user in its UI. + */ + public static final int SHOW_FORCED = 0x00002; + + /** + * Request that any soft input part of the input method be shown to the user. + * + * @param flags Provide additional information about the show request. + * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set. + */ + public void showSoftInput(int flags); + + /** + * Request that any soft input part of the input method be hidden from the user. + */ + public void hideSoftInput(); +} diff --git a/core/java/android/view/inputmethod/InputMethodInfo.aidl b/core/java/android/view/inputmethod/InputMethodInfo.aidl new file mode 100644 index 0000000..5f4d6b6 --- /dev/null +++ b/core/java/android/view/inputmethod/InputMethodInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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.inputmethod; + +parcelable InputMethodInfo; diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java new file mode 100644 index 0000000..4e98591 --- /dev/null +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2007-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.inputmethod; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Printer; +import android.util.Xml; + +import java.io.IOException; + +/** + * This class is used to specify meta information of an input method. + */ +public final class InputMethodInfo implements Parcelable { + static final String TAG = "InputMethodMetaInfo"; + + /** + * The Service that implements this input method component. + */ + final ResolveInfo mService; + + /** + * The unique string Id to identify the input method. This is generated + * from the input method component. + */ + final String mId; + + /** + * The input method setting activity's name, used by the system settings to + * launch the setting activity of this input method. + */ + final String mSettingsActivityName; + + /** + * The resource in the input method's .apk that holds a boolean indicating + * whether it should be considered the default input method for this + * system. This is a resource ID instead of the final value so that it + * can change based on the configuration (in particular locale). + */ + final int mIsDefaultResId; + + /** + * Constructor. + * + * @param context The Context in which we are parsing the input method. + * @param service The ResolveInfo returned from the package manager about + * this input method's component. + */ + public InputMethodInfo(Context context, ResolveInfo service) + throws XmlPullParserException, IOException { + mService = service; + ServiceInfo si = service.serviceInfo; + mId = new ComponentName(si.packageName, si.name).flattenToShortString(); + + PackageManager pm = context.getPackageManager(); + String settingsActivityComponent = null; + int isDefaultResId = 0; + + XmlResourceParser parser = null; + try { + parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA); + if (parser == null) { + throw new XmlPullParserException("No " + + InputMethod.SERVICE_META_DATA + " meta-data"); + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"input-method".equals(nodeName)) { + throw new XmlPullParserException( + "Meta-data does not start with input-method tag"); + } + + TypedArray sa = context.getResources().obtainAttributes(attrs, + com.android.internal.R.styleable.InputMethod); + settingsActivityComponent = sa.getString( + com.android.internal.R.styleable.InputMethod_settingsActivity); + isDefaultResId = sa.getResourceId( + com.android.internal.R.styleable.InputMethod_isDefault, 0); + sa.recycle(); + } finally { + if (parser != null) parser.close(); + } + + mSettingsActivityName = settingsActivityComponent; + mIsDefaultResId = isDefaultResId; + } + + InputMethodInfo(Parcel source) { + mId = source.readString(); + mSettingsActivityName = source.readString(); + mIsDefaultResId = source.readInt(); + mService = ResolveInfo.CREATOR.createFromParcel(source); + } + + /** + * Temporary API for creating a built-in input method. + */ + public InputMethodInfo(String packageName, String className, + CharSequence label, String settingsActivity) { + ResolveInfo ri = new ResolveInfo(); + ServiceInfo si = new ServiceInfo(); + ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.enabled = true; + si.applicationInfo = ai; + si.enabled = true; + si.packageName = packageName; + si.name = className; + si.exported = true; + si.nonLocalizedLabel = label; + ri.serviceInfo = si; + mService = ri; + mId = new ComponentName(si.packageName, si.name).flattenToShortString(); + mSettingsActivityName = settingsActivity; + mIsDefaultResId = 0; + } + + /** + * Return a unique ID for this input method. The ID is generated from + * the package and class name implementing the method. + */ + public String getId() { + return mId; + } + + /** + * Return the .apk package that implements this input method. + */ + public String getPackageName() { + return mService.serviceInfo.packageName; + } + + /** + * Return the class name of the service component that implements + * this input method. + */ + public String getServiceName() { + return mService.serviceInfo.name; + } + + /** + * Return the component of the service that implements this input + * method. + */ + public ComponentName getComponent() { + return new ComponentName(mService.serviceInfo.packageName, + mService.serviceInfo.name); + } + + /** + * Load the user-displayed label for this input method. + * + * @param pm Supply a PackageManager used to load the input method's + * resources. + */ + public CharSequence loadLabel(PackageManager pm) { + return mService.loadLabel(pm); + } + + /** + * Load the user-displayed icon for this input method. + * + * @param pm Supply a PackageManager used to load the input method's + * resources. + */ + public Drawable loadIcon(PackageManager pm) { + return mService.loadIcon(pm); + } + + /** + * Return the class name of an activity that provides a settings UI for + * the input method. You can launch this activity be starting it with + * an {@link android.content.Intent} whose action is MAIN and with an + * explicit {@link android.content.ComponentName} + * composed of {@link #getPackageName} and the class name returned here. + * + * <p>A null will be returned if there is no settings activity associated + * with the input method. + */ + public String getSettingsActivity() { + return mSettingsActivityName; + } + + /** + * Return the resource identifier of a resource inside of this input + * method's .apk that determines whether it should be considered a + * default input method for the system. + */ + public int getIsDefaultResourceId() { + return mIsDefaultResId; + } + + public void dump(Printer pw, String prefix) { + pw.println(prefix + "mId=" + mId + + " mSettingsActivityName=" + mSettingsActivityName); + pw.println(prefix + "mIsDefaultResId=0x" + + Integer.toHexString(mIsDefaultResId)); + pw.println(prefix + "Service:"); + mService.dump(pw, prefix + " "); + } + + @Override + public String toString() { + return "InputMethodMetaInfo{" + mId + + ", settings: " + + mSettingsActivityName + "}"; + } + + /** + * Used to test whether the given parameter object is an + * {@link InputMethodInfo} and its Id is the same to this one. + * + * @return true if the given parameter object is an + * {@link InputMethodInfo} and its Id is the same to this one. + */ + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o == null) return false; + + if (!(o instanceof InputMethodInfo)) return false; + + InputMethodInfo obj = (InputMethodInfo) o; + return mId.equals(obj.mId); + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + dest.writeString(mSettingsActivityName); + dest.writeInt(mIsDefaultResId); + mService.writeToParcel(dest, flags); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<InputMethodInfo> CREATOR = new Parcelable.Creator<InputMethodInfo>() { + public InputMethodInfo createFromParcel(Parcel source) { + return new InputMethodInfo(source); + } + + public InputMethodInfo[] newArray(int size) { + return new InputMethodInfo[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java new file mode 100644 index 0000000..0aa1d6c --- /dev/null +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -0,0 +1,1239 @@ +/* + * Copyright (C) 2007-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.inputmethod; + +import android.content.Context; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.PrintWriterPrinter; +import android.util.Printer; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewRoot; + +import com.android.internal.os.HandlerCaller; +import com.android.internal.view.IInputConnectionWrapper; +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethodCallback; +import com.android.internal.view.IInputMethodClient; +import com.android.internal.view.IInputMethodManager; +import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.InputBindResult; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Central system API to the overall input method framework (IMF) architecture, + * which arbitrates interaction between applications and the current input method. + * You can retrieve an instance of this interface with + * {@link Context#getSystemService(String) Context.getSystemService()}. + * + * <p>Topics covered here: + * <ol> + * <li><a href="#ArchitectureOverview">Architecture Overview</a> + * </ol> + * + * <a name="ArchitectureOverview"></a> + * <h3>Architecture Overview</h3> + * + * <p>There are three primary parties involved in the input method + * framework (IMF) architecture:</p> + * + * <ul> + * <li> The <strong>input method manager</strong> as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + * <li> An <strong>input method (IME)</strong> implements a particular + * interaction model allowing the user to generate text. The system binds + * to the current input method that is use, causing it to be created and run, + * and tells it when to hide and show its UI. Only one IME is running at a time. + * <li> Multiple <strong>client applications</strong> arbitrate with the input + * method manager for input focus and control over the state of the IME. Only + * one such client is ever active (working with the IME) at a time. + * </ul> + * + * + * <a name="Applications"></a> + * <h3>Applications</h3> + * + * <p>In most cases, applications that are using the standard + * {@link android.widget.TextView} or its subclasses will have little they need + * to do to work well with soft input methods. The main things you need to + * be aware of are:</p> + * + * <ul> + * <li> Properly set the {@link android.R.attr#inputType} if your editable + * text views, so that the input method will have enough context to help the + * user in entering text into them. + * <li> Deal well with losing screen space when the input method is + * displayed. Ideally an application should handle its window being resized + * smaller, but it can rely on the system performing panning of the window + * if needed. You should set the {@link android.R.attr#windowSoftInputMode} + * attribute on your activity or the corresponding values on windows you + * create to help the system determine whether to pan or resize (it will + * try to determine this automatically but may get it wrong). + * <li> You can also control the preferred soft input state (open, closed, etc) + * for your window using the same {@link android.R.attr#windowSoftInputMode} + * attribute. + * </ul> + * + * <p>More finer-grained control is available through the APIs here to directly + * interact with the IMF and its IME -- either showing or hiding the input + * area, letting the user pick an input method, etc.</p> + * + * <p>For the rare people amongst us writing their own text editors, you + * will need to implement {@link android.view.View#onCreateInputConnection} + * to return a new instance of your own {@link InputConnection} interface + * allowing the IME to interact with your editor.</p> + * + * + * <a name="InputMethods"></a> + * <h3>Input Methods</h3> + * + * <p>An input method (IME) is implemented + * as a {@link android.app.Service}, typically deriving from + * {@link android.inputmethodservice.InputMethodService}. It must provide + * the core {@link InputMethod} interface, though this is normally handled by + * {@link android.inputmethodservice.InputMethodService} and implementors will + * only need to deal with the higher-level API there.</p> + * + * See the {@link android.inputmethodservice.InputMethodService} class for + * more information on implementing IMEs. + * + * + * <a name="Security"></a> + * <h3>Security</h3> + * + * <p>There are a lot of security issues associated with input methods, + * since they essentially have freedom to completely drive the UI and monitor + * everything the user enters. The Android input method framework also allows + * arbitrary third party IMEs, so care must be taken to restrict their + * selection and interactions.</p> + * + * <p>Here are some key points about the security architecture behind the + * IMF:</p> + * + * <ul> + * <li> <p>Only the system is allowed to directly access an IME's + * {@link InputMethod} interface, via the + * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission. This is + * enforced in the system by not binding to an input method service that does + * not require this permission, so the system can guarantee no other untrusted + * clients are accessing the current input method outside of its control.</p> + * + * <li> <p>There may be many client processes of the IMF, but only one may + * be active at a time. The inactive clients can not interact with key + * parts of the IMF through the mechanisms described below.</p> + * + * <li> <p>Clients of an input method are only given access to its + * {@link InputMethodSession} interface. One instance of this interface is + * created for each client, and only calls from the session associated with + * the active client will be processed by the current IME. This is enforced + * by {@link android.inputmethodservice.AbstractInputMethodService} for normal + * IMEs, but must be explicitly handled by an IME that is customizing the + * raw {@link InputMethodSession} implementation.</p> + * + * <li> <p>Only the active client's {@link InputConnection} will accept + * operations. The IMF tells each client process whether it is active, and + * the framework enforces that in inactive processes calls on to the current + * InputConnection will be ignored. This ensures that the current IME can + * only deliver events and text edits to the UI that the user sees as + * being in focus.</p> + * + * <li> <p>An IME can never interact with an {@link InputConnection} while + * the screen is off. This is enforced by making all clients inactive while + * the screen is off, and prevents bad IMEs from driving the UI when the user + * can not be aware of its behavior.</p> + * + * <li> <p>A client application can ask that the system let the user pick a + * new IME, but can not programmatically switch to one itself. This avoids + * malicious applications from switching the user to their own IME, which + * remains running when the user navigates away to another application. An + * IME, on the other hand, <em>is</em> allowed to programmatically switch + * the system to another IME, since it already has full control of user + * input.</p> + * + * <li> <p>The user must explicitly enable a new IME in settings before + * they can switch to it, to confirm with the system that they know about it + * and want to make it available for use.</p> + * </ul> + */ +public final class InputMethodManager { + static final boolean DEBUG = false; + static final String TAG = "InputMethodManager"; + + static final Object mInstanceSync = new Object(); + static InputMethodManager mInstance; + + final IInputMethodManager mService; + final Looper mMainLooper; + + // For scheduling work on the main thread. This also serves as our + // global lock. + final H mH; + + // Our generic input connection if the current target does not have its own. + final IInputContext mIInputContext; + + /** + * True if this input method client is active, initially false. + */ + boolean mActive = false; + + /** + * Set whenever this client becomes inactive, to know we need to reset + * state with the IME then next time we receive focus. + */ + boolean mHasBeenInactive = true; + + /** + * As reported by IME through InputConnection. + */ + boolean mFullscreenMode; + + // ----------------------------------------------------------- + + /** + * This is the root view of the overall window that currently has input + * method focus. + */ + View mCurRootView; + /** + * This is the view that should currently be served by an input method, + * regardless of the state of setting that up. + */ + View mServedView; + /** + * This is then next view that will be served by the input method, when + * we get around to updating things. + */ + View mNextServedView; + /** + * True if we should restart input in the next served view, even if the + * view hasn't actually changed from the current serve view. + */ + boolean mNextServedNeedsStart; + /** + * This is set when we are in the process of connecting, to determine + * when we have actually finished. + */ + boolean mServedConnecting; + /** + * This is non-null when we have connected the served view; it holds + * the attributes that were last retrieved from the served view and given + * to the input connection. + */ + EditorInfo mCurrentTextBoxAttribute; + /** + * The InputConnection that was last retrieved from the served view. + */ + InputConnection mServedInputConnection; + /** + * The completions that were last provided by the served view. + */ + CompletionInfo[] mCompletions; + + // Cursor position on the screen. + Rect mTmpCursorRect = new Rect(); + Rect mCursorRect = new Rect(); + int mCursorSelStart; + int mCursorSelEnd; + int mCursorCandStart; + int mCursorCandEnd; + + // ----------------------------------------------------------- + + /** + * Sequence number of this binding, as returned by the server. + */ + int mBindSequence = -1; + /** + * ID of the method we are bound to. + */ + String mCurId; + /** + * The actual instance of the method to make calls on it. + */ + IInputMethodSession mCurMethod; + + // ----------------------------------------------------------- + + static final int MSG_DUMP = 1; + + class H extends Handler { + H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DUMP: { + HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; + try { + doDump((FileDescriptor)args.arg1, + (PrintWriter)args.arg2, (String[])args.arg3); + } catch (RuntimeException e) { + ((PrintWriter)args.arg2).println("Exception: " + e); + } + synchronized (args.arg4) { + ((CountDownLatch)args.arg4).countDown(); + } + return; + } + } + } + } + + class ControlledInputConnectionWrapper extends IInputConnectionWrapper { + public ControlledInputConnectionWrapper(Looper mainLooper, InputConnection conn) { + super(mainLooper, conn); + } + + public boolean isActive() { + return mActive; + } + } + + final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + // No need to check for dump permission, since we only give this + // interface to the system. + + CountDownLatch latch = new CountDownLatch(1); + HandlerCaller.SomeArgs sargs = new HandlerCaller.SomeArgs(); + sargs.arg1 = fd; + sargs.arg2 = fout; + sargs.arg3 = args; + sargs.arg4 = latch; + mH.sendMessage(mH.obtainMessage(MSG_DUMP, sargs)); + try { + if (!latch.await(5, TimeUnit.SECONDS)) { + fout.println("Timeout waiting for dump"); + } + } catch (InterruptedException e) { + fout.println("Interrupted waiting for dump"); + } + } + + public void setUsingInputMethod(boolean state) { + } + + public void onBindMethod(InputBindResult res) { + synchronized (mH) { + if (mBindSequence < 0 || mBindSequence != res.sequence) { + Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence + + ", given seq=" + res.sequence); + return; + } + + mCurMethod = res.method; + mCurId = res.id; + mBindSequence = res.sequence; + } + startInputInner(); + } + + public void onUnbindMethod(int sequence) { + synchronized (mH) { + if (mBindSequence == sequence) { + if (false) { + // XXX the server has already unbound! + if (mCurMethod != null && mCurrentTextBoxAttribute != null) { + try { + mCurMethod.finishInput(); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + clearBindingLocked(); + + // If we were actively using the last input method, then + // we would like to re-connect to the next input method. + if (mServedView != null && mServedView.isFocused()) { + mServedConnecting = true; + } + } + startInputInner(); + } + } + + public void setActive(boolean active) { + synchronized (mH) { + mActive = active; + mFullscreenMode = false; + if (!active) { + // Some other client has starting using the IME, so note + // that this happened and make sure our own editor's + // state is reset. + mHasBeenInactive = true; + try { + // Note that finishComposingText() is allowed to run + // even when we are not active. + mIInputContext.finishComposingText(); + } catch (RemoteException e) { + } + } + } + } + }; + + final InputConnection mDummyInputConnection = new BaseInputConnection(this, true); + + InputMethodManager(IInputMethodManager service, Looper looper) { + mService = service; + mMainLooper = looper; + mH = new H(looper); + mIInputContext = new ControlledInputConnectionWrapper(looper, + mDummyInputConnection); + + if (mInstance == null) { + mInstance = this; + } + } + + /** + * Retrieve the global InputMethodManager instance, creating it if it + * doesn't already exist. + * @hide + */ + static public InputMethodManager getInstance(Context context) { + synchronized (mInstanceSync) { + if (mInstance != null) { + return mInstance; + } + IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE); + IInputMethodManager service = IInputMethodManager.Stub.asInterface(b); + mInstance = new InputMethodManager(service, context.getMainLooper()); + } + return mInstance; + } + + /** + * Private optimization: retrieve the global InputMethodManager instance, + * if it exists. + * @hide + */ + static public InputMethodManager peekInstance() { + return mInstance; + } + + /** @hide */ + public IInputMethodClient getClient() { + return mClient; + } + + /** @hide */ + public IInputContext getInputContext() { + return mIInputContext; + } + + public List<InputMethodInfo> getInputMethodList() { + try { + return mService.getInputMethodList(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public List<InputMethodInfo> getEnabledInputMethodList() { + try { + return mService.getEnabledInputMethodList(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void showStatusIcon(IBinder imeToken, String packageName, int iconId) { + try { + mService.updateStatusIcon(imeToken, packageName, iconId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + public void hideStatusIcon(IBinder imeToken) { + try { + mService.updateStatusIcon(imeToken, null, 0); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** @hide */ + public void setFullscreenMode(boolean fullScreen) { + mFullscreenMode = fullScreen; + } + + /** + * Allows you to discover whether the attached input method is running + * in fullscreen mode. Return true if it is fullscreen, entirely covering + * your UI, else returns false. + */ + public boolean isFullscreenMode() { + return mFullscreenMode; + } + + /** + * Return true if the given view is the currently active view for the + * input method. + */ + public boolean isActive(View view) { + checkFocus(); + synchronized (mH) { + return mServedView == view && mCurrentTextBoxAttribute != null; + } + } + + /** + * Return true if any view is currently active in the input method. + */ + public boolean isActive() { + checkFocus(); + synchronized (mH) { + return mServedView != null && mCurrentTextBoxAttribute != null; + } + } + + /** + * Return true if the currently served view is accepting full text edits. + * If false, it has no input connection, so can only handle raw key events. + */ + public boolean isAcceptingText() { + checkFocus(); + return mServedInputConnection != null; + } + + /** + * Reset all of the state associated with being bound to an input method. + */ + void clearBindingLocked() { + clearConnectionLocked(); + mBindSequence = -1; + mCurId = null; + mCurMethod = null; + } + + /** + * Reset all of the state associated with a served view being connected + * to an input method + */ + void clearConnectionLocked() { + mCurrentTextBoxAttribute = null; + mServedInputConnection = null; + } + + /** + * Disconnect any existing input connection, clearing the served view. + */ + void finishInputLocked() { + mNextServedView = null; + if (mServedView != null) { + if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); + + if (mCurrentTextBoxAttribute != null) { + try { + mService.finishInput(mClient); + } catch (RemoteException e) { + } + } + + if (mServedInputConnection != null) { + // We need to tell the previously served view that it is no + // longer the input target, so it can reset its state. Schedule + // this call on its window's Handler so it will be on the correct + // thread and outside of our lock. + Handler vh = mServedView.getHandler(); + if (vh != null) { + // This will result in a call to reportFinishInputConnection() + // below. + vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION, + mServedInputConnection)); + } + } + + mServedView = null; + mCompletions = null; + mServedConnecting = false; + clearConnectionLocked(); + } + } + + /** + * Called from the FINISH_INPUT_CONNECTION message above. + * @hide + */ + public void reportFinishInputConnection(InputConnection ic) { + if (mServedInputConnection != ic) { + ic.finishComposingText(); + } + } + + public void displayCompletions(View view, CompletionInfo[] completions) { + checkFocus(); + synchronized (mH) { + if (mServedView != view) { + return; + } + + mCompletions = completions; + if (mCurMethod != null) { + try { + mCurMethod.displayCompletions(mCompletions); + } catch (RemoteException e) { + } + } + } + } + + public void updateExtractedText(View view, int token, ExtractedText text) { + checkFocus(); + synchronized (mH) { + if (mServedView != view) { + return; + } + + if (mCurMethod != null) { + try { + mCurMethod.updateExtractedText(token, text); + } catch (RemoteException e) { + } + } + } + } + + /** + * Flag for {@link #showSoftInput} to indicate that this is an implicit + * request to show the input window, not as the result of a direct request + * by the user. The window may not be shown in this case. + */ + public static final int SHOW_IMPLICIT = 0x0001; + + /** + * Flag for {@link #showSoftInput} to indicate that the user has forced + * the input method open (such as by long-pressing menu) so it should + * not be closed until they explicitly do so. + */ + public static final int SHOW_FORCED = 0x0002; + + /** + * Explicitly request that the current input method's soft input area be + * shown to the user, if needed. Call this if the user interacts with + * your view in such a way that they have expressed they would like to + * start performing input into it. + * + * @param view The currently focused view, which would like to receive + * soft keyboard input. + * @param flags Provides additional operating flags. Currently may be + * 0 or have the {@link #SHOW_IMPLICIT} bit set. + */ + public void showSoftInput(View view, int flags) { + checkFocus(); + synchronized (mH) { + if (mServedView != view) { + return; + } + + try { + mService.showSoftInput(mClient, flags); + } catch (RemoteException e) { + } + } + } + + /** @hide */ + public void showSoftInputUnchecked(int flags) { + try { + mService.showSoftInput(mClient, flags); + } catch (RemoteException e) { + } + } + + /** + * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft + * input window should only be hidden if it was not explicitly shown + * by the user. + */ + public static final int HIDE_IMPLICIT_ONLY = 0x0001; + + /** + * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft + * input window should normally be hidden, unless it was originally + * shown with {@link #SHOW_FORCED}. + */ + public static final int HIDE_NOT_ALWAYS = 0x0002; + + /** + * Request to hide the soft input window from the context of the window + * that is currently accepting input. This should be called as a result + * of the user doing some actually than fairly explicitly requests to + * have the input window hidden. + * + * @param windowToken The token of the window that is making the request, + * as returned by {@link View#getWindowToken() View.getWindowToken()}. + * @param flags Provides additional operating flags. Currently may be + * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. + */ + public void hideSoftInputFromWindow(IBinder windowToken, int flags) { + checkFocus(); + synchronized (mH) { + if (mServedView == null || mServedView.getWindowToken() != windowToken) { + return; + } + + try { + mService.hideSoftInput(mClient, flags); + } catch (RemoteException e) { + } + } + } + + /** + * If the input method is currently connected to the given view, + * restart it with its new contents. You should call this when the text + * within your view changes outside of the normal input method or key + * input flow, such as when an application calls TextView.setText(). + * + * @param view The view whose text has changed. + */ + public void restartInput(View view) { + checkFocus(); + synchronized (mH) { + if (mServedView != view) { + return; + } + + mServedConnecting = true; + } + + startInputInner(); + } + + void startInputInner() { + final View view; + synchronized (mH) { + view = mServedView; + + // Make sure we have a window token for the served view. + if (DEBUG) Log.v(TAG, "Starting input: view=" + view); + if (view == null) { + if (DEBUG) Log.v(TAG, "ABORT input: no served view!"); + return; + } + } + + // Now we need to get an input connection from the served view. + // This is complicated in a couple ways: we can't be holding our lock + // when calling out to the view, and we need to make sure we call into + // the view on the same thread that is driving its view hierarchy. + Handler vh = view.getHandler(); + if (vh == null) { + // If the view doesn't have a handler, something has changed out + // from under us, so just bail. + if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!"); + return; + } + if (vh.getLooper() != Looper.myLooper()) { + // The view is running on a different thread than our own, so + // we need to reschedule our work for over there. + if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread"); + vh.post(new Runnable() { + public void run() { + startInputInner(); + } + }); + } + + // Okay we are now ready to call into the served view and have it + // do its stuff. + // Life is good: let's hook everything up! + EditorInfo tba = new EditorInfo(); + tba.packageName = view.getContext().getPackageName(); + tba.fieldId = view.getId(); + InputConnection ic = view.onCreateInputConnection(tba); + if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); + + synchronized (mH) { + // Now that we are locked again, validate that our state hasn't + // changed. + if (mServedView != view || !mServedConnecting) { + // Something else happened, so abort. + if (DEBUG) Log.v(TAG, + "Starting input: finished by someone else (view=" + + mServedView + " conn=" + mServedConnecting + ")"); + return; + } + + // If we already have a text box, then this view is already + // connected so we want to restart it. + final boolean initial = mCurrentTextBoxAttribute == null; + + // Hook 'em up and let 'er rip. + mCurrentTextBoxAttribute = tba; + mServedConnecting = false; + mServedInputConnection = ic; + IInputContext servedContext; + if (ic != null) { + mCursorSelStart = tba.initialSelStart; + mCursorSelEnd = tba.initialSelEnd; + mCursorCandStart = -1; + mCursorCandEnd = -1; + mCursorRect.setEmpty(); + servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic); + } else { + servedContext = null; + } + + try { + if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic=" + + ic + " tba=" + tba + " initial=" + initial); + InputBindResult res = mService.startInput(mClient, + servedContext, tba, initial, mCurMethod == null); + if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); + if (res != null) { + if (res.id != null) { + mBindSequence = res.sequence; + mCurMethod = res.method; + } else { + // This means there is no input method available. + if (DEBUG) Log.v(TAG, "ABORT input: no input method!"); + return; + } + } + if (mCurMethod != null && mCompletions != null) { + try { + mCurMethod.displayCompletions(mCompletions); + } catch (RemoteException e) { + } + } + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + + /** + * When the focused window is dismissed, this method is called to finish the + * input method started before. + * @hide + */ + public void windowDismissed(IBinder appWindowToken) { + checkFocus(); + synchronized (mH) { + if (mServedView != null && + mServedView.getWindowToken() == appWindowToken) { + finishInputLocked(); + } + } + } + + /** + * Call this when a view receives focus. + * @hide + */ + public void focusIn(View view) { + synchronized (mH) { + focusInLocked(view); + } + } + + void focusInLocked(View view) { + if (DEBUG) Log.v(TAG, "focusIn: " + view); + + if (mCurRootView != view.getRootView()) { + // This is a request from a window that isn't in the window with + // IME focus, so ignore it. + if (DEBUG) Log.v(TAG, "Not IME target window, ignoring"); + return; + } + + mNextServedView = view; + scheduleCheckFocusLocked(view); + } + + /** + * Call this when a view loses focus. + * @hide + */ + public void focusOut(View view) { + synchronized (mH) { + if (DEBUG) Log.v(TAG, "focusOut: " + view + + " mServedView=" + mServedView + + " winFocus=" + view.hasWindowFocus()); + if (mServedView == view) { + // The following code would auto-hide the IME if we end up + // with no more views with focus. This can happen, however, + // whenever we go into touch mode, so it ends up hiding + // at times when we don't really want it to. For now it + // seems better to just turn it all off. + if (false && view.hasWindowFocus()) { + mNextServedView = null; + scheduleCheckFocusLocked(view); + } + } + } + } + + void scheduleCheckFocusLocked(View view) { + Handler vh = view.getHandler(); + if (vh != null && !vh.hasMessages(ViewRoot.CHECK_FOCUS)) { + // This will result in a call to checkFocus() below. + vh.sendMessage(vh.obtainMessage(ViewRoot.CHECK_FOCUS)); + } + } + + /** + * @hide + */ + public void checkFocus() { + // This is called a lot, so short-circuit before locking. + if (mServedView == mNextServedView && !mNextServedNeedsStart) { + return; + } + + InputConnection ic = null; + synchronized (mH) { + if (mServedView == mNextServedView && !mNextServedNeedsStart) { + return; + } + if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView + + " next=" + mNextServedView + + " restart=" + mNextServedNeedsStart); + + mNextServedNeedsStart = false; + if (mNextServedView == null) { + finishInputLocked(); + // In this case, we used to have a focused view on the window, + // but no longer do. We should make sure the input method is + // no longer shown, since it serves no purpose. + closeCurrentInput(); + return; + } + + ic = mServedInputConnection; + + mServedView = mNextServedView; + mCurrentTextBoxAttribute = null; + mCompletions = null; + mServedConnecting = true; + } + + if (ic != null) { + ic.finishComposingText(); + } + + startInputInner(); + } + + void closeCurrentInput() { + try { + mService.hideSoftInput(mClient, HIDE_NOT_ALWAYS); + } catch (RemoteException e) { + } + } + + /** + * Called by ViewRoot when its window gets input focus. + * @hide + */ + public void onWindowFocus(View rootView, View focusedView, int softInputMode, + boolean first, int windowFlags) { + synchronized (mH) { + if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView + + " softInputMode=" + softInputMode + + " first=" + first + " flags=#" + + Integer.toHexString(windowFlags)); + if (mHasBeenInactive) { + if (DEBUG) Log.v(TAG, "Has been inactive! Starting fresh"); + mHasBeenInactive = false; + mNextServedNeedsStart = true; + } + focusInLocked(focusedView != null ? focusedView : rootView); + } + + checkFocus(); + + synchronized (mH) { + try { + final boolean isTextEditor = focusedView != null && + focusedView.onCheckIsTextEditor(); + mService.windowGainedFocus(mClient, focusedView != null, + isTextEditor, softInputMode, first, windowFlags); + } catch (RemoteException e) { + } + } + } + + /** @hide */ + public void startGettingWindowFocus(View rootView) { + synchronized (mH) { + mCurRootView = rootView; + } + } + + /** + * Report the current selection range. + */ + public void updateSelection(View view, int selStart, int selEnd, + int candidatesStart, int candidatesEnd) { + checkFocus(); + synchronized (mH) { + if (mServedView != view || mCurrentTextBoxAttribute == null + || mCurMethod == null) { + return; + } + + if (mCursorSelStart != selStart || mCursorSelEnd != selEnd + || mCursorCandStart != candidatesStart + || mCursorCandEnd != candidatesEnd) { + if (DEBUG) Log.d(TAG, "updateSelection"); + + try { + if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod); + mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd, + selStart, selEnd, candidatesStart, candidatesEnd); + mCursorSelStart = selStart; + mCursorSelEnd = selEnd; + mCursorCandStart = candidatesStart; + mCursorCandEnd = candidatesEnd; + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + } + + /** + * Returns true if the current input method wants to watch the location + * of the input editor's cursor in its window. + */ + public boolean isWatchingCursor(View view) { + return false; + } + + /** + * Report the current cursor location in its window. + */ + public void updateCursor(View view, int left, int top, int right, int bottom) { + checkFocus(); + synchronized (mH) { + if (mServedView != view || mCurrentTextBoxAttribute == null + || mCurMethod == null) { + return; + } + + mTmpCursorRect.set(left, top, right, bottom); + if (!mCursorRect.equals(mTmpCursorRect)) { + if (DEBUG) Log.d(TAG, "updateCursor"); + + try { + if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod); + mCurMethod.updateCursor(mTmpCursorRect); + mCursorRect.set(mTmpCursorRect); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + } + + /** + * Call {@link InputMethodSession#appPrivateCommand(String, Bundle) + * InputMethodSession.appPrivateCommand()} on the current Input Method. + * @param view Optional View that is sending the command, or null if + * you want to send the command regardless of the view that is attached + * to the input method. + * @param action Name of the command to be performed. This <em>must</em> + * be a scoped name, i.e. prefixed with a package name you own, so that + * different developers will not create conflicting commands. + * @param data Any data to include with the command. + */ + public void sendAppPrivateCommand(View view, String action, Bundle data) { + checkFocus(); + synchronized (mH) { + if ((view != null && mServedView != view) + || mCurrentTextBoxAttribute == null || mCurMethod == null) { + return; + } + try { + if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data); + mCurMethod.appPrivateCommand(action, data); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + + /** + * Force switch to a new input method component. This can only be called + * from the currently active input method, as validated by the given token. + * @param token Supplies the identifying token given to an input method + * when it was started, which allows it to perform this operation on + * itself. + * @param id The unique identifier for the new input method to be switched to. + */ + public void setInputMethod(IBinder token, String id) { + try { + mService.setInputMethod(token, id); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Close/hide the input method's soft input area, so the user no longer + * sees it or can interact with it. This can only be called + * from the currently active input method, as validated by the given token. + * + * @param token Supplies the identifying token given to an input method + * when it was started, which allows it to perform this operation on + * itself. + * @param flags Provides additional operating flags. Currently may be + * 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set. + */ + public void hideSoftInputFromInputMethod(IBinder token, int flags) { + try { + mService.hideMySoftInput(token, flags); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * @hide + */ + public void dispatchKeyEvent(Context context, int seq, KeyEvent key, + IInputMethodCallback callback) { + synchronized (mH) { + if (DEBUG) Log.d(TAG, "dispatchKeyEvent"); + + if (mCurMethod == null) { + try { + callback.finishedEvent(seq, false); + } catch (RemoteException e) { + } + return; + } + + if (key.getAction() == KeyEvent.ACTION_DOWN + && key.getKeyCode() == KeyEvent.KEYCODE_SYM) { + showInputMethodPicker(); + try { + callback.finishedEvent(seq, true); + } catch (RemoteException e) { + } + return; + } + try { + if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); + mCurMethod.dispatchKeyEvent(seq, key, callback); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); + try { + callback.finishedEvent(seq, false); + } catch (RemoteException ex) { + } + } + } + } + + /** + * @hide + */ + void dispatchTrackballEvent(Context context, int seq, MotionEvent motion, + IInputMethodCallback callback) { + synchronized (mH) { + if (DEBUG) Log.d(TAG, "dispatchTrackballEvent"); + + if (mCurMethod == null || mCurrentTextBoxAttribute == null) { + try { + callback.finishedEvent(seq, false); + } catch (RemoteException e) { + } + return; + } + + try { + if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod); + mCurMethod.dispatchTrackballEvent(seq, motion, callback); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e); + try { + callback.finishedEvent(seq, false); + } catch (RemoteException ex) { + } + } + } + } + + public void showInputMethodPicker() { + synchronized (mH) { + try { + mService.showInputMethodPickerFromClient(mClient); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + + void doDump(FileDescriptor fd, PrintWriter fout, String[] args) { + final Printer p = new PrintWriterPrinter(fout); + p.println("Input method client state for " + this + ":"); + + p.println(" mService=" + mService); + p.println(" mMainLooper=" + mMainLooper); + p.println(" mIInputContext=" + mIInputContext); + p.println(" mActive=" + mActive + + " mHasBeenInactive=" + mHasBeenInactive + + " mBindSequence=" + mBindSequence + + " mCurId=" + mCurId); + p.println(" mCurMethod=" + mCurMethod); + p.println(" mCurRootView=" + mCurRootView); + p.println(" mServedView=" + mServedView); + p.println(" mNextServedNeedsStart=" + mNextServedNeedsStart + + " mNextServedView=" + mNextServedView); + p.println(" mServedConnecting=" + mServedConnecting); + if (mCurrentTextBoxAttribute != null) { + p.println(" mCurrentTextBoxAttribute:"); + mCurrentTextBoxAttribute.dump(p, " "); + } else { + p.println(" mCurrentTextBoxAttribute: null"); + } + p.println(" mServedInputConnection=" + mServedInputConnection); + p.println(" mCompletions=" + mCompletions); + p.println(" mCursorRect=" + mCursorRect); + p.println(" mCursorSelStart=" + mCursorSelStart + + " mCursorSelEnd=" + mCursorSelEnd + + " mCursorCandStart=" + mCursorCandStart + + " mCursorCandEnd=" + mCursorCandEnd); + } +} diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java new file mode 100644 index 0000000..b5bbaff --- /dev/null +++ b/core/java/android/view/inputmethod/InputMethodSession.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2007-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.inputmethod; + +import android.graphics.Rect; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; + +/** + * The InputMethodSession interface provides the per-client functionality + * of {@link InputMethod} that is safe to expose to applications. + * + * <p>Applications will not normally use this interface themselves, instead + * relying on the standard interaction provided by + * {@link android.widget.TextView} and {@link android.widget.EditText}. + */ +public interface InputMethodSession { + + public interface EventCallback { + void finishedEvent(int seq, boolean handled); + } + + /** + * This method is called when the application would like to stop + * receiving text input. + */ + public void finishInput(); + + /** + * This method is called when the selection or cursor in the current + * target input field has changed. + * + * @param oldSelStart The previous text offset of the cursor selection + * start position. + * @param oldSelEnd The previous text offset of the cursor selection + * end position. + * @param newSelStart The new text offset of the cursor selection + * start position. + * @param newSelEnd The new text offset of the cursor selection + * end position. + * @param candidatesStart The text offset of the current candidate + * text start position. + * @param candidatesEnd The text offset of the current candidate + * text end position. + */ + public void updateSelection(int oldSelStart, int oldSelEnd, + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd); + + /** + * This method is called when cursor location of the target input field + * has changed within its window. This is not normally called, but will + * only be reported if requested by the input method. + * + * @param newCursor The rectangle of the cursor currently being shown in + * the input field's window coordinates. + */ + public void updateCursor(Rect newCursor); + + /** + * Called by a text editor that performs auto completion, to tell the + * input method about the completions it has available. This can be used + * by the input method to display them to the user to select the text to + * be inserted. + * + * @param completions Array of text completions that are available, starting with + * the best. If this array is null, any existing completions will be + * removed. + */ + public void displayCompletions(CompletionInfo[] completions); + + /** + * Called by a text editor to report its new extracted text when its + * contents change. This will only be called if the input method + * calls {@link InputConnection#getExtractedText(ExtractedTextRequest, int) + * InputConnection.getExtractedText()} with the option to report updates. + * + * @param token The input method supplied token for identifying its request. + * @param text The new extracted text. + */ + public void updateExtractedText(int token, ExtractedText text); + + /** + * This method is called when a key is pressed. When done with the event, + * the implementation must call back on <var>callback</var> with its + * result. + * + * <p> + * If the input method wants to handle this event, return true, otherwise + * return false and the caller (i.e. the application) will handle the event. + * + * @param event The key event. + * + * @return Whether the input method wants to handle this event. + * + * @see #dispatchKeyUp + * @see android.view.KeyEvent + */ + public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback); + + /** + * This method is called when there is a track ball event. + * + * <p> + * If the input method wants to handle this event, return true, otherwise + * return false and the caller (i.e. the application) will handle the event. + * + * @param event The motion event. + * + * @return Whether the input method wants to handle this event. + * + * @see android.view.MotionEvent + */ + public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback); + + /** + * Process a private command sent from the application to the input method. + * This can be used to provide domain-specific features that are + * only known between certain input methods and their clients. + * + * @param action Name of the command to be performed. This <em>must</em> + * be a scoped name, i.e. prefixed with a package name you own, so that + * different developers will not create conflicting commands. + * @param data Any data to include with the command. + */ + public void appPrivateCommand(String action, Bundle data); +} diff --git a/core/java/android/view/inputmethod/package.html b/core/java/android/view/inputmethod/package.html new file mode 100644 index 0000000..328c7b3 --- /dev/null +++ b/core/java/android/view/inputmethod/package.html @@ -0,0 +1,12 @@ +<html> +<body> +Framework classes for interaction between views and input methods (such +as soft keyboards). See {@link android.view.inputmethod.InputMethodManager} for +an overview. In most cases the main classes here are not needed for +most applications, since they are dealt with for you by +{@link android.widget.TextView}. When implementing a custom text editor, +however, you will need to implement the +{@link android.view.inputmethod.InputConnection} class to allow the current +input method to interact with your view. +</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 |