summaryrefslogtreecommitdiffstats
path: root/core/java/android/view
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
commit9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch)
treed88beb88001f2482911e3d28e43833b50e4b4e97 /core/java/android/view
parentd83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff)
downloadframeworks_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')
-rw-r--r--core/java/android/view/AbsSavedState.java89
-rw-r--r--core/java/android/view/ContextMenu.java91
-rw-r--r--core/java/android/view/ContextThemeWrapper.java103
-rw-r--r--core/java/android/view/Display.java121
-rw-r--r--core/java/android/view/FocusFinder.java480
-rw-r--r--core/java/android/view/FocusFinderHelper.java59
-rw-r--r--core/java/android/view/GestureDetector.java564
-rw-r--r--core/java/android/view/Gravity.java306
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java45
-rw-r--r--core/java/android/view/IApplicationToken.aidl28
-rw-r--r--core/java/android/view/IOnKeyguardExitResult.aidl25
-rw-r--r--core/java/android/view/IRotationWatcher.aidl25
-rw-r--r--core/java/android/view/IWindow.aidl59
-rw-r--r--core/java/android/view/IWindowManager.aidl136
-rw-r--r--core/java/android/view/IWindowSession.aidl111
-rw-r--r--core/java/android/view/InflateException.java40
-rw-r--r--core/java/android/view/KeyCharacterMap.java545
-rw-r--r--core/java/android/view/KeyEvent.aidl20
-rw-r--r--core/java/android/view/KeyEvent.java886
-rw-r--r--core/java/android/view/LayoutInflater.java744
-rw-r--r--core/java/android/view/Menu.java441
-rw-r--r--core/java/android/view/MenuInflater.java325
-rw-r--r--core/java/android/view/MenuItem.java384
-rw-r--r--core/java/android/view/MotionEvent.aidl20
-rw-r--r--core/java/android/view/MotionEvent.java685
-rwxr-xr-xcore/java/android/view/OrientationEventListener.java174
-rw-r--r--core/java/android/view/OrientationListener.java110
-rw-r--r--core/java/android/view/RawInputEvent.java170
-rw-r--r--core/java/android/view/RemotableViewMethod.java35
-rw-r--r--core/java/android/view/SoundEffectConstants.java57
-rw-r--r--core/java/android/view/SubMenu.java103
-rw-r--r--core/java/android/view/Surface.aidl20
-rw-r--r--core/java/android/view/Surface.java298
-rw-r--r--core/java/android/view/SurfaceHolder.java284
-rw-r--r--core/java/android/view/SurfaceSession.java49
-rw-r--r--core/java/android/view/SurfaceView.java614
-rw-r--r--core/java/android/view/TouchDelegate.java153
-rw-r--r--core/java/android/view/VelocityTracker.java215
-rw-r--r--core/java/android/view/View.java8076
-rw-r--r--core/java/android/view/ViewConfiguration.java424
-rw-r--r--core/java/android/view/ViewDebug.java1128
-rw-r--r--core/java/android/view/ViewGroup.java3478
-rw-r--r--core/java/android/view/ViewManager.java27
-rw-r--r--core/java/android/view/ViewParent.java211
-rw-r--r--core/java/android/view/ViewRoot.java2872
-rw-r--r--core/java/android/view/ViewStub.java279
-rw-r--r--core/java/android/view/ViewTreeObserver.java613
-rw-r--r--core/java/android/view/VolumePanel.java410
-rw-r--r--core/java/android/view/Window.java1005
-rwxr-xr-xcore/java/android/view/WindowManager.aidl21
-rw-r--r--core/java/android/view/WindowManager.java959
-rw-r--r--core/java/android/view/WindowManagerImpl.java364
-rw-r--r--core/java/android/view/WindowManagerPolicy.java796
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java180
-rw-r--r--core/java/android/view/animation/AccelerateDecelerateInterpolator.java37
-rw-r--r--core/java/android/view/animation/AccelerateInterpolator.java62
-rw-r--r--core/java/android/view/animation/AlphaAnimation.java81
-rw-r--r--core/java/android/view/animation/Animation.java925
-rw-r--r--core/java/android/view/animation/AnimationSet.java472
-rw-r--r--core/java/android/view/animation/AnimationUtils.java329
-rw-r--r--core/java/android/view/animation/CycleInterpolator.java47
-rw-r--r--core/java/android/view/animation/DecelerateInterpolator.java61
-rw-r--r--core/java/android/view/animation/GridLayoutAnimationController.java424
-rw-r--r--core/java/android/view/animation/Interpolator.java39
-rw-r--r--core/java/android/view/animation/LayoutAnimationController.java435
-rw-r--r--core/java/android/view/animation/LinearInterpolator.java37
-rw-r--r--core/java/android/view/animation/RotateAnimation.java165
-rw-r--r--core/java/android/view/animation/ScaleAnimation.java186
-rw-r--r--core/java/android/view/animation/Transformation.java147
-rw-r--r--core/java/android/view/animation/TranslateAnimation.java171
-rwxr-xr-xcore/java/android/view/animation/package.html20
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java571
-rw-r--r--core/java/android/view/inputmethod/CompletionInfo.aidl19
-rw-r--r--core/java/android/view/inputmethod/CompletionInfo.java131
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.aidl19
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java263
-rw-r--r--core/java/android/view/inputmethod/ExtractedText.aidl19
-rw-r--r--core/java/android/view/inputmethod/ExtractedText.java102
-rw-r--r--core/java/android/view/inputmethod/ExtractedTextRequest.aidl19
-rw-r--r--core/java/android/view/inputmethod/ExtractedTextRequest.java70
-rw-r--r--core/java/android/view/inputmethod/InputBinding.aidl19
-rw-r--r--core/java/android/view/inputmethod/InputBinding.java152
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java320
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java200
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.aidl19
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java291
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java1239
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java142
-rw-r--r--core/java/android/view/inputmethod/package.html12
-rw-r--r--core/java/android/view/package.html6
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 &egrave;.
+ */
+ 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 &egrave;.
+ */
+ 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>&nbsp;&nbsp;&nbsp;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>&nbsp;&nbsp;&nbsp;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>
+ * &lt;Button id="@+id/my_button"
+ * android:layout_width="wrap_content"
+ * android:layout_height="wrap_content"
+ * android:text="@string/my_button_text"/&gt;
+ * </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 &lt;size, mode&gt; 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>
+ * &lt;ViewStub android:id="@+id/stub"
+ * android:inflatedId="@+id/subTree"
+ * android:layout="@layout/mySubTree"
+ * android:layout_width="120dip"
+ * android:layout_height="40dip" /&gt;
+ * </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>&lt;{@link android.R.styleable#InputMethod input-method}&gt;</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