summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOren Blasberg <orenb@google.com>2015-09-01 21:11:04 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-09-01 21:11:04 +0000
commit6b49d300346d130d2681cbd3de7165989c3a33f2 (patch)
tree821b4e9ff1820958688e5c28b02662a20d0c56fd
parent8056e4132416265196c9f0f3cc0e86bceeb7d907 (diff)
parentf44d90b5c247f0629201d1fa322b83fa55b20608 (diff)
Merge "Pull out widget helpers into their own classes."
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java6
-rw-r--r--core/java/android/widget/ActivityChooserView.java4
-rw-r--r--core/java/android/widget/DropDownListView.java344
-rw-r--r--core/java/android/widget/ForwardingListener.java299
-rw-r--r--core/java/android/widget/ListPopupWindow.java581
-rw-r--r--core/java/android/widget/MenuPopupWindow.java4
-rw-r--r--core/java/android/widget/PopupMenu.java4
-rw-r--r--core/java/android/widget/Spinner.java4
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java10
-rw-r--r--core/java/com/android/internal/view/menu/MenuAdapter.java109
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java71
-rw-r--r--core/java/com/android/internal/view/menu/ShowableListMenu.java35
12 files changed, 815 insertions, 656 deletions
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index a5696eefa1db..64c41039bea5 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -37,7 +37,6 @@ import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ListPopupWindow.ForwardingListener;
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.ActionMenuItemView;
import com.android.internal.view.menu.BaseMenuPresenter;
@@ -45,6 +44,7 @@ import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.ShowableListMenu;
import com.android.internal.view.menu.SubMenuBuilder;
import java.util.ArrayList;
@@ -828,7 +828,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
setOnTouchListener(new ForwardingListener(this) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
if (mOverflowPopup == null) {
return null;
}
@@ -1003,7 +1003,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
}
}
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index f34ad71ea76a..f5c46db94f4f 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -17,6 +17,7 @@
package android.widget;
import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
import android.annotation.StringRes;
import android.content.Context;
@@ -37,7 +38,6 @@ import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ActivityChooserModel.ActivityChooserModelClient;
-import android.widget.ListPopupWindow.ForwardingListener;
/**
* This class is a view for choosing an activity for handling a given {@link Intent}.
@@ -263,7 +263,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
});
expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return getListPopupWindow();
}
diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java
new file mode 100644
index 000000000000..553651308e9c
--- /dev/null
+++ b/core/java/android/widget/DropDownListView.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+
+import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.IntProperty;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.TextView;
+import android.widget.ListView;
+
+
+/**
+ * Wrapper class for a ListView. This wrapper can hijack the focus to
+ * make sure the list uses the appropriate drawables and states when
+ * displayed on screen within a drop down. The focus is never actually
+ * passed to the drop down in this mode; the list only looks focused.
+ *
+ * @hide
+ */
+public class DropDownListView extends ListView {
+ /** Duration in milliseconds of the drag-to-open click animation. */
+ private static final long CLICK_ANIM_DURATION = 150;
+
+ /** Target alpha value for drag-to-open click animation. */
+ private static final int CLICK_ANIM_ALPHA = 0x80;
+
+ /** Wrapper around Drawable's <code>alpha</code> property. */
+ private static final IntProperty<Drawable> DRAWABLE_ALPHA =
+ new IntProperty<Drawable>("alpha") {
+ @Override
+ public void setValue(Drawable object, int value) {
+ object.setAlpha(value);
+ }
+
+ @Override
+ public Integer get(Drawable object) {
+ return object.getAlpha();
+ }
+ };
+
+ /*
+ * WARNING: This is a workaround for a touch mode issue.
+ *
+ * Touch mode is propagated lazily to windows. This causes problems in
+ * the following scenario:
+ * - Type something in the AutoCompleteTextView and get some results
+ * - Move down with the d-pad to select an item in the list
+ * - Move up with the d-pad until the selection disappears
+ * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+ * and get new results; you are now in touch mode
+ * - The selection comes back on the first item in the list, even though
+ * the list is supposed to be in touch mode
+ *
+ * Using the soft keyboard triggers the touch mode change but that change
+ * is propagated to our window only after the first list layout, therefore
+ * after the list attempts to resurrect the selection.
+ *
+ * The trick to work around this issue is to pretend the list is in touch
+ * mode when we know that the selection should not appear, that is when
+ * we know the user moved the selection away from the list.
+ *
+ * This boolean is set to true whenever we explicitly hide the list's
+ * selection and reset to false whenever we know the user moved the
+ * selection back to the list.
+ *
+ * When this boolean is true, isInTouchMode() returns true, otherwise it
+ * returns super.isInTouchMode().
+ */
+ private boolean mListSelectionHidden;
+
+ /**
+ * True if this wrapper should fake focus.
+ */
+ private boolean mHijackFocus;
+
+ /** Whether to force drawing of the pressed state selector. */
+ private boolean mDrawsInPressedState;
+
+ /** Current drag-to-open click animation, if any. */
+ private Animator mClickAnimation;
+
+ /** Helper for drag-to-open auto scrolling. */
+ private AbsListViewAutoScroller mScrollHelper;
+
+ /**
+ * Creates a new list view wrapper.
+ *
+ * @param context this view's context
+ */
+ public DropDownListView(Context context, boolean hijackFocus) {
+ this(context, hijackFocus, com.android.internal.R.attr.dropDownListViewStyle);
+ }
+
+ /**
+ * Creates a new list view wrapper.
+ *
+ * @param context this view's context
+ */
+ public DropDownListView(Context context, boolean hijackFocus, int defStyleAttr) {
+ super(context, null, defStyleAttr);
+ mHijackFocus = hijackFocus;
+ // TODO: Add an API to control this
+ setCacheColorHint(0); // Transparent, since the background drawable could be anything.
+ }
+
+ /**
+ * Handles forwarded events.
+ *
+ * @param activePointerId id of the pointer that activated forwarding
+ * @return whether the event was handled
+ */
+ public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+ boolean handledEvent = true;
+ boolean clearPressedItem = false;
+
+ final int actionMasked = event.getActionMasked();
+ switch (actionMasked) {
+ case MotionEvent.ACTION_CANCEL:
+ handledEvent = false;
+ break;
+ case MotionEvent.ACTION_UP:
+ handledEvent = false;
+ // $FALL-THROUGH$
+ case MotionEvent.ACTION_MOVE:
+ final int activeIndex = event.findPointerIndex(activePointerId);
+ if (activeIndex < 0) {
+ handledEvent = false;
+ break;
+ }
+
+ final int x = (int) event.getX(activeIndex);
+ final int y = (int) event.getY(activeIndex);
+ final int position = pointToPosition(x, y);
+ if (position == INVALID_POSITION) {
+ clearPressedItem = true;
+ break;
+ }
+
+ final View child = getChildAt(position - getFirstVisiblePosition());
+ setPressedItem(child, position, x, y);
+ handledEvent = true;
+
+ if (actionMasked == MotionEvent.ACTION_UP) {
+ clickPressedItem(child, position);
+ }
+ break;
+ }
+
+ // Failure to handle the event cancels forwarding.
+ if (!handledEvent || clearPressedItem) {
+ clearPressedItem();
+ }
+
+ // Manage automatic scrolling.
+ if (handledEvent) {
+ if (mScrollHelper == null) {
+ mScrollHelper = new AbsListViewAutoScroller(this);
+ }
+ mScrollHelper.setEnabled(true);
+ mScrollHelper.onTouch(this, event);
+ } else if (mScrollHelper != null) {
+ mScrollHelper.setEnabled(false);
+ }
+
+ return handledEvent;
+ }
+
+ /**
+ * Sets whether the list selection is hidden, as part of a workaround for a touch mode issue
+ * (see the declaration for mListSelectionHidden).
+ * @param listSelectionHidden
+ */
+ public void setListSelectionHidden(boolean listSelectionHidden) {
+ this.mListSelectionHidden = listSelectionHidden;
+ }
+
+ /**
+ * Starts an alpha animation on the selector. When the animation ends,
+ * the list performs a click on the item.
+ */
+ private void clickPressedItem(final View child, final int position) {
+ final long id = getItemIdAtPosition(position);
+ final Animator anim = ObjectAnimator.ofInt(
+ mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF);
+ anim.setDuration(CLICK_ANIM_DURATION);
+ anim.setInterpolator(new AccelerateDecelerateInterpolator());
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ performItemClick(child, position, id);
+ }
+ });
+ anim.start();
+
+ if (mClickAnimation != null) {
+ mClickAnimation.cancel();
+ }
+ mClickAnimation = anim;
+ }
+
+ private void clearPressedItem() {
+ mDrawsInPressedState = false;
+ setPressed(false);
+ updateSelectorState();
+
+ final View motionView = getChildAt(mMotionPosition - mFirstPosition);
+ if (motionView != null) {
+ motionView.setPressed(false);
+ }
+
+ if (mClickAnimation != null) {
+ mClickAnimation.cancel();
+ mClickAnimation = null;
+ }
+ }
+
+ private void setPressedItem(View child, int position, float x, float y) {
+ mDrawsInPressedState = true;
+
+ // Ordering is essential. First, update the container's pressed state.
+ drawableHotspotChanged(x, y);
+ if (!isPressed()) {
+ setPressed(true);
+ }
+
+ // Next, run layout if we need to stabilize child positions.
+ if (mDataChanged) {
+ layoutChildren();
+ }
+
+ // Manage the pressed view based on motion position. This allows us to
+ // play nicely with actual touch and scroll events.
+ final View motionView = getChildAt(mMotionPosition - mFirstPosition);
+ if (motionView != null && motionView != child && motionView.isPressed()) {
+ motionView.setPressed(false);
+ }
+ mMotionPosition = position;
+
+ // Offset for child coordinates.
+ final float childX = x - child.getLeft();
+ final float childY = y - child.getTop();
+ child.drawableHotspotChanged(childX, childY);
+ if (!child.isPressed()) {
+ child.setPressed(true);
+ }
+
+ // Ensure that keyboard focus starts from the last touched position.
+ setSelectedPositionInt(position);
+ positionSelectorLikeTouch(position, child, x, y);
+
+ // Refresh the drawable state to reflect the new pressed state,
+ // which will also update the selector state.
+ refreshDrawableState();
+
+ if (mClickAnimation != null) {
+ mClickAnimation.cancel();
+ mClickAnimation = null;
+ }
+ }
+
+ @Override
+ boolean touchModeDrawsInPressedState() {
+ return mDrawsInPressedState || super.touchModeDrawsInPressedState();
+ }
+
+ /**
+ * Avoids jarring scrolling effect by ensuring that list elements
+ * made of a text view fit on a single line.
+ *
+ * @param position the item index in the list to get a view for
+ * @return the view for the specified item
+ */
+ @Override
+ View obtainView(int position, boolean[] isScrap) {
+ View view = super.obtainView(position, isScrap);
+
+ if (view instanceof TextView) {
+ ((TextView) view).setHorizontallyScrolling(true);
+ }
+
+ return view;
+ }
+
+ @Override
+ public boolean isInTouchMode() {
+ // WARNING: Please read the comment where mListSelectionHidden is declared
+ return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+ }
+
+ /**
+ * Returns the focus state in the drop down.
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasWindowFocus() {
+ return mHijackFocus || super.hasWindowFocus();
+ }
+
+ /**
+ * Returns the focus state in the drop down.
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean isFocused() {
+ return mHijackFocus || super.isFocused();
+ }
+
+ /**
+ * Returns the focus state in the drop down.
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasFocus() {
+ return mHijackFocus || super.hasFocus();
+ }
+} \ No newline at end of file
diff --git a/core/java/android/widget/ForwardingListener.java b/core/java/android/widget/ForwardingListener.java
new file mode 100644
index 000000000000..fd7140f779fe
--- /dev/null
+++ b/core/java/android/widget/ForwardingListener.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+import com.android.internal.view.menu.ShowableListMenu;
+
+/**
+ * Abstract class that forwards touch events to a {@link ListPopupWindow}.
+ *
+ * @hide
+ */
+public abstract class ForwardingListener
+ implements View.OnTouchListener, View.OnAttachStateChangeListener {
+
+ /** Scaled touch slop, used for detecting movement outside bounds. */
+ private final float mScaledTouchSlop;
+
+ /** Timeout before disallowing intercept on the source's parent. */
+ private final int mTapTimeout;
+
+ /** Timeout before accepting a long-press to start forwarding. */
+ private final int mLongPressTimeout;
+
+ /** Source view from which events are forwarded. */
+ private final View mSrc;
+
+ /** Runnable used to prevent conflicts with scrolling parents. */
+ private Runnable mDisallowIntercept;
+
+ /** Runnable used to trigger forwarding on long-press. */
+ private Runnable mTriggerLongPress;
+
+ /** Whether this listener is currently forwarding touch events. */
+ private boolean mForwarding;
+
+ /**
+ * Whether forwarding was initiated by a long-press. If so, we won't
+ * force the window to dismiss when the touch stream ends.
+ */
+ private boolean mWasLongPress;
+
+ /** The id of the first pointer down in the current event stream. */
+ private int mActivePointerId;
+
+ public ForwardingListener(View src) {
+ mSrc = src;
+ mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+
+ // Use a medium-press timeout. Halfway between tap and long-press.
+ mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
+
+ src.addOnAttachStateChangeListener(this);
+ }
+
+ /**
+ * Returns the popup to which this listener is forwarding events.
+ * <p>
+ * Override this to return the correct popup. If the popup is displayed
+ * asynchronously, you may also need to override
+ * {@link #onForwardingStopped} to prevent premature cancellation of
+ * forwarding.
+ *
+ * @return the popup to which this listener is forwarding events
+ */
+ public abstract ShowableListMenu getPopup();
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ final boolean wasForwarding = mForwarding;
+ final boolean forwarding;
+ if (wasForwarding) {
+ forwarding = onTouchForwarded(event) || !onForwardingStopped();
+ } else {
+ forwarding = onTouchObserved(event) && onForwardingStarted();
+
+ if (forwarding) {
+ // Make sure we cancel any ongoing source event stream.
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+ 0.0f, 0.0f, 0);
+ mSrc.onTouchEvent(e);
+ e.recycle();
+ }
+ }
+
+ mForwarding = forwarding;
+ return forwarding || wasForwarding;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mForwarding = false;
+ mActivePointerId = MotionEvent.INVALID_POINTER_ID;
+
+ if (mDisallowIntercept != null) {
+ mSrc.removeCallbacks(mDisallowIntercept);
+ }
+ }
+
+ /**
+ * Called when forwarding would like to start.
+ * <p>
+ * By default, this will show the popup returned by {@link #getPopup()}.
+ * It may be overridden to perform another action, like clicking the
+ * source view or preparing the popup before showing it.
+ *
+ * @return true to start forwarding, false otherwise
+ */
+ protected boolean onForwardingStarted() {
+ final ShowableListMenu popup = getPopup();
+ if (popup != null && !popup.isShowing()) {
+ popup.show();
+ }
+ return true;
+ }
+
+ /**
+ * Called when forwarding would like to stop.
+ * <p>
+ * By default, this will dismiss the popup returned by
+ * {@link #getPopup()}. It may be overridden to perform some other
+ * action.
+ *
+ * @return true to stop forwarding, false otherwise
+ */
+ protected boolean onForwardingStopped() {
+ final ShowableListMenu popup = getPopup();
+ if (popup != null && popup.isShowing()) {
+ popup.dismiss();
+ }
+ return true;
+ }
+
+ /**
+ * Observes motion events and determines when to start forwarding.
+ *
+ * @param srcEvent motion event in source view coordinates
+ * @return true to start forwarding motion events, false otherwise
+ */
+ private boolean onTouchObserved(MotionEvent srcEvent) {
+ final View src = mSrc;
+ if (!src.isEnabled()) {
+ return false;
+ }
+
+ final int actionMasked = srcEvent.getActionMasked();
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = srcEvent.getPointerId(0);
+ mWasLongPress = false;
+
+ if (mDisallowIntercept == null) {
+ mDisallowIntercept = new DisallowIntercept();
+ }
+ src.postDelayed(mDisallowIntercept, mTapTimeout);
+
+ if (mTriggerLongPress == null) {
+ mTriggerLongPress = new TriggerLongPress();
+ }
+ src.postDelayed(mTriggerLongPress, mLongPressTimeout);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
+ if (activePointerIndex >= 0) {
+ final float x = srcEvent.getX(activePointerIndex);
+ final float y = srcEvent.getY(activePointerIndex);
+
+ // Has the pointer moved outside of the view?
+ if (!src.pointInView(x, y, mScaledTouchSlop)) {
+ clearCallbacks();
+
+ // Don't let the parent intercept our events.
+ src.getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ clearCallbacks();
+ break;
+ }
+
+ return false;
+ }
+
+ private void clearCallbacks() {
+ if (mTriggerLongPress != null) {
+ mSrc.removeCallbacks(mTriggerLongPress);
+ }
+
+ if (mDisallowIntercept != null) {
+ mSrc.removeCallbacks(mDisallowIntercept);
+ }
+ }
+
+ private void onLongPress() {
+ clearCallbacks();
+
+ final View src = mSrc;
+ if (!src.isEnabled() || src.isLongClickable()) {
+ // Ignore long-press if the view is disabled or has its own
+ // handler.
+ return;
+ }
+
+ if (!onForwardingStarted()) {
+ return;
+ }
+
+ // Don't let the parent intercept our events.
+ src.getParent().requestDisallowInterceptTouchEvent(true);
+
+ // Make sure we cancel any ongoing source event stream.
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ src.onTouchEvent(e);
+ e.recycle();
+
+ mForwarding = true;
+ mWasLongPress = true;
+ }
+
+ /**
+ * Handles forwarded motion events and determines when to stop
+ * forwarding.
+ *
+ * @param srcEvent motion event in source view coordinates
+ * @return true to continue forwarding motion events, false to cancel
+ */
+ private boolean onTouchForwarded(MotionEvent srcEvent) {
+ final View src = mSrc;
+ final ShowableListMenu popup = getPopup();
+ if (popup == null || !popup.isShowing()) {
+ return false;
+ }
+
+ final DropDownListView dst = (DropDownListView) popup.getListView();
+ if (dst == null || !dst.isShown()) {
+ return false;
+ }
+
+ // Convert event to destination-local coordinates.
+ final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
+ src.toGlobalMotionEvent(dstEvent);
+ dst.toLocalMotionEvent(dstEvent);
+
+ // Forward converted event to destination view, then recycle it.
+ final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
+ dstEvent.recycle();
+
+ // Always cancel forwarding when the touch stream ends.
+ final int action = srcEvent.getActionMasked();
+ final boolean keepForwarding = action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL;
+
+ return handled && keepForwarding;
+ }
+
+ private class DisallowIntercept implements Runnable {
+ @Override
+ public void run() {
+ final ViewParent parent = mSrc.getParent();
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
+ private class TriggerLongPress implements Runnable {
+ @Override
+ public void run() {
+ onLongPress();
+ }
+ }
+} \ No newline at end of file
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index a02efcff4197..3d07d87dfe70 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -25,7 +25,6 @@ import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.IntProperty;
@@ -36,13 +35,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
import java.util.Locale;
@@ -58,7 +57,7 @@ import java.util.Locale;
* @see android.widget.AutoCompleteTextView
* @see android.widget.Spinner
*/
-public class ListPopupWindow {
+public class ListPopupWindow implements ShowableListMenu {
private static final String TAG = "ListPopupWindow";
private static final boolean DEBUG = false;
@@ -580,6 +579,7 @@ public class ListPopupWindow {
* Show the popup list. If the list is already showing, this method
* will recalculate the popup's size and position.
*/
+ @Override
public void show() {
int height = buildDropDown();
@@ -671,6 +671,7 @@ public class ListPopupWindow {
/**
* Dismiss the popup window.
*/
+ @Override
public void dismiss() {
mPopup.dismiss();
removePromptView();
@@ -732,7 +733,7 @@ public class ListPopupWindow {
public void setSelection(int position) {
DropDownListView list = mDropDownList;
if (isShowing() && list != null) {
- list.mListSelectionHidden = false;
+ list.setListSelectionHidden(false);
list.setSelection(position);
if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
list.setItemChecked(position, true);
@@ -748,7 +749,7 @@ public class ListPopupWindow {
final DropDownListView list = mDropDownList;
if (list != null) {
// WARNING: Please read the comment where mListSelectionHidden is declared
- list.mListSelectionHidden = true;
+ list.setListSelectionHidden(true);
list.hideSelector();
list.requestLayout();
}
@@ -757,6 +758,7 @@ public class ListPopupWindow {
/**
* @return {@code true} if the popup is currently showing, {@code false} otherwise.
*/
+ @Override
public boolean isShowing() {
return mPopup.isShowing();
}
@@ -842,6 +844,7 @@ public class ListPopupWindow {
* @return The {@link ListView} displayed within the popup window.
* Only valid when {@link #isShowing()} == {@code true}.
*/
+ @Override
public ListView getListView() {
return mDropDownList;
}
@@ -911,7 +914,7 @@ public class ListPopupWindow {
} else {
// WARNING: Please read the comment where mListSelectionHidden
// is declared
- mDropDownList.mListSelectionHidden = false;
+ mDropDownList.setListSelectionHidden(false);
}
consumed = mDropDownList.onKeyDown(keyCode, event);
@@ -1037,7 +1040,7 @@ public class ListPopupWindow {
public OnTouchListener createDragToOpenListener(View src) {
return new ForwardingListener(src) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return ListPopupWindow.this;
}
};
@@ -1088,7 +1091,7 @@ public class ListPopupWindow {
DropDownListView dropDownList = mDropDownList;
if (dropDownList != null) {
- dropDownList.mListSelectionHidden = false;
+ dropDownList.setListSelectionHidden(false);
}
}
}
@@ -1219,568 +1222,6 @@ public class ListPopupWindow {
return listContent + otherHeights;
}
- /**
- * Abstract class that forwards touch events to a {@link ListPopupWindow}.
- *
- * @hide
- */
- public static abstract class ForwardingListener
- implements View.OnTouchListener, View.OnAttachStateChangeListener {
- /** Scaled touch slop, used for detecting movement outside bounds. */
- private final float mScaledTouchSlop;
-
- /** Timeout before disallowing intercept on the source's parent. */
- private final int mTapTimeout;
-
- /** Timeout before accepting a long-press to start forwarding. */
- private final int mLongPressTimeout;
-
- /** Source view from which events are forwarded. */
- private final View mSrc;
-
- /** Runnable used to prevent conflicts with scrolling parents. */
- private Runnable mDisallowIntercept;
-
- /** Runnable used to trigger forwarding on long-press. */
- private Runnable mTriggerLongPress;
-
- /** Whether this listener is currently forwarding touch events. */
- private boolean mForwarding;
-
- /**
- * Whether forwarding was initiated by a long-press. If so, we won't
- * force the window to dismiss when the touch stream ends.
- */
- private boolean mWasLongPress;
-
- /** The id of the first pointer down in the current event stream. */
- private int mActivePointerId;
-
- public ForwardingListener(View src) {
- mSrc = src;
- mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
- mTapTimeout = ViewConfiguration.getTapTimeout();
-
- // Use a medium-press timeout. Halfway between tap and long-press.
- mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
-
- src.addOnAttachStateChangeListener(this);
- }
-
- /**
- * Returns the popup to which this listener is forwarding events.
- * <p>
- * Override this to return the correct popup. If the popup is displayed
- * asynchronously, you may also need to override
- * {@link #onForwardingStopped} to prevent premature cancelation of
- * forwarding.
- *
- * @return the popup to which this listener is forwarding events
- */
- public abstract ListPopupWindow getPopup();
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- final boolean wasForwarding = mForwarding;
- final boolean forwarding;
- if (wasForwarding) {
- forwarding = onTouchForwarded(event) || !onForwardingStopped();
- } else {
- forwarding = onTouchObserved(event) && onForwardingStarted();
-
- if (forwarding) {
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
- 0.0f, 0.0f, 0);
- mSrc.onTouchEvent(e);
- e.recycle();
- }
- }
-
- mForwarding = forwarding;
- return forwarding || wasForwarding;
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- mForwarding = false;
- mActivePointerId = MotionEvent.INVALID_POINTER_ID;
-
- if (mDisallowIntercept != null) {
- mSrc.removeCallbacks(mDisallowIntercept);
- }
- }
-
- /**
- * Called when forwarding would like to start.
- * <p>
- * By default, this will show the popup returned by {@link #getPopup()}.
- * It may be overridden to perform another action, like clicking the
- * source view or preparing the popup before showing it.
- *
- * @return true to start forwarding, false otherwise
- */
- protected boolean onForwardingStarted() {
- final ListPopupWindow popup = getPopup();
- if (popup != null && !popup.isShowing()) {
- popup.show();
- }
- return true;
- }
-
- /**
- * Called when forwarding would like to stop.
- * <p>
- * By default, this will dismiss the popup returned by
- * {@link #getPopup()}. It may be overridden to perform some other
- * action.
- *
- * @return true to stop forwarding, false otherwise
- */
- protected boolean onForwardingStopped() {
- final ListPopupWindow popup = getPopup();
- if (popup != null && popup.isShowing()) {
- popup.dismiss();
- }
- return true;
- }
-
- /**
- * Observes motion events and determines when to start forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to start forwarding motion events, false otherwise
- */
- private boolean onTouchObserved(MotionEvent srcEvent) {
- final View src = mSrc;
- if (!src.isEnabled()) {
- return false;
- }
-
- final int actionMasked = srcEvent.getActionMasked();
- switch (actionMasked) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = srcEvent.getPointerId(0);
- mWasLongPress = false;
-
- if (mDisallowIntercept == null) {
- mDisallowIntercept = new DisallowIntercept();
- }
- src.postDelayed(mDisallowIntercept, mTapTimeout);
-
- if (mTriggerLongPress == null) {
- mTriggerLongPress = new TriggerLongPress();
- }
- src.postDelayed(mTriggerLongPress, mLongPressTimeout);
- break;
- case MotionEvent.ACTION_MOVE:
- final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
- if (activePointerIndex >= 0) {
- final float x = srcEvent.getX(activePointerIndex);
- final float y = srcEvent.getY(activePointerIndex);
-
- // Has the pointer has moved outside of the view?
- if (!src.pointInView(x, y, mScaledTouchSlop)) {
- clearCallbacks();
-
- // Don't let the parent intercept our events.
- src.getParent().requestDisallowInterceptTouchEvent(true);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- clearCallbacks();
- break;
- }
-
- return false;
- }
-
- private void clearCallbacks() {
- if (mTriggerLongPress != null) {
- mSrc.removeCallbacks(mTriggerLongPress);
- }
-
- if (mDisallowIntercept != null) {
- mSrc.removeCallbacks(mDisallowIntercept);
- }
- }
-
- private void onLongPress() {
- clearCallbacks();
-
- final View src = mSrc;
- if (!src.isEnabled() || src.isLongClickable()) {
- // Ignore long-press if the view is disabled or has its own
- // handler.
- return;
- }
-
- if (!onForwardingStarted()) {
- return;
- }
-
- // Don't let the parent intercept our events.
- src.getParent().requestDisallowInterceptTouchEvent(true);
-
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- src.onTouchEvent(e);
- e.recycle();
-
- mForwarding = true;
- mWasLongPress = true;
- }
-
- /**
- * Handled forwarded motion events and determines when to stop
- * forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to continue forwarding motion events, false to cancel
- */
- private boolean onTouchForwarded(MotionEvent srcEvent) {
- final View src = mSrc;
- final ListPopupWindow popup = getPopup();
- if (popup == null || !popup.isShowing()) {
- return false;
- }
-
- final DropDownListView dst = popup.mDropDownList;
- if (dst == null || !dst.isShown()) {
- return false;
- }
-
- // Convert event to destination-local coordinates.
- final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
- src.toGlobalMotionEvent(dstEvent);
- dst.toLocalMotionEvent(dstEvent);
-
- // Forward converted event to destination view, then recycle it.
- final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
- dstEvent.recycle();
-
- // Always cancel forwarding when the touch stream ends.
- final int action = srcEvent.getActionMasked();
- final boolean keepForwarding = action != MotionEvent.ACTION_UP
- && action != MotionEvent.ACTION_CANCEL;
-
- return handled && keepForwarding;
- }
-
- private class DisallowIntercept implements Runnable {
- @Override
- public void run() {
- final ViewParent parent = mSrc.getParent();
- parent.requestDisallowInterceptTouchEvent(true);
- }
- }
-
- private class TriggerLongPress implements Runnable {
- @Override
- public void run() {
- onLongPress();
- }
- }
- }
-
- /**
- * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
- * make sure the list uses the appropriate drawables and states when
- * displayed on screen within a drop down. The focus is never actually
- * passed to the drop down in this mode; the list only looks focused.</p>
- */
- static class DropDownListView extends ListView {
- /** Duration in milliseconds of the drag-to-open click animation. */
- private static final long CLICK_ANIM_DURATION = 150;
-
- /** Target alpha value for drag-to-open click animation. */
- private static final int CLICK_ANIM_ALPHA = 0x80;
-
- /** Wrapper around Drawable's <code>alpha</code> property. */
- private static final IntProperty<Drawable> DRAWABLE_ALPHA =
- new IntProperty<Drawable>("alpha") {
- @Override
- public void setValue(Drawable object, int value) {
- object.setAlpha(value);
- }
-
- @Override
- public Integer get(Drawable object) {
- return object.getAlpha();
- }
- };
-
- /*
- * WARNING: This is a workaround for a touch mode issue.
- *
- * Touch mode is propagated lazily to windows. This causes problems in
- * the following scenario:
- * - Type something in the AutoCompleteTextView and get some results
- * - Move down with the d-pad to select an item in the list
- * - Move up with the d-pad until the selection disappears
- * - Type more text in the AutoCompleteTextView *using the soft keyboard*
- * and get new results; you are now in touch mode
- * - The selection comes back on the first item in the list, even though
- * the list is supposed to be in touch mode
- *
- * Using the soft keyboard triggers the touch mode change but that change
- * is propagated to our window only after the first list layout, therefore
- * after the list attempts to resurrect the selection.
- *
- * The trick to work around this issue is to pretend the list is in touch
- * mode when we know that the selection should not appear, that is when
- * we know the user moved the selection away from the list.
- *
- * This boolean is set to true whenever we explicitly hide the list's
- * selection and reset to false whenever we know the user moved the
- * selection back to the list.
- *
- * When this boolean is true, isInTouchMode() returns true, otherwise it
- * returns super.isInTouchMode().
- */
- private boolean mListSelectionHidden;
-
- /**
- * True if this wrapper should fake focus.
- */
- private boolean mHijackFocus;
-
- /** Whether to force drawing of the pressed state selector. */
- private boolean mDrawsInPressedState;
-
- /** Current drag-to-open click animation, if any. */
- private Animator mClickAnimation;
-
- /** Helper for drag-to-open auto scrolling. */
- private AbsListViewAutoScroller mScrollHelper;
-
- /**
- * <p>Creates a new list view wrapper.</p>
- *
- * @param context this view's context
- */
- public DropDownListView(Context context, boolean hijackFocus) {
- super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
- mHijackFocus = hijackFocus;
- // TODO: Add an API to control this
- setCacheColorHint(0); // Transparent, since the background drawable could be anything.
- }
-
- /**
- * Handles forwarded events.
- *
- * @param activePointerId id of the pointer that activated forwarding
- * @return whether the event was handled
- */
- public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
- boolean handledEvent = true;
- boolean clearPressedItem = false;
-
- final int actionMasked = event.getActionMasked();
- switch (actionMasked) {
- case MotionEvent.ACTION_CANCEL:
- handledEvent = false;
- break;
- case MotionEvent.ACTION_UP:
- handledEvent = false;
- // $FALL-THROUGH$
- case MotionEvent.ACTION_MOVE:
- final int activeIndex = event.findPointerIndex(activePointerId);
- if (activeIndex < 0) {
- handledEvent = false;
- break;
- }
-
- final int x = (int) event.getX(activeIndex);
- final int y = (int) event.getY(activeIndex);
- final int position = pointToPosition(x, y);
- if (position == INVALID_POSITION) {
- clearPressedItem = true;
- break;
- }
-
- final View child = getChildAt(position - getFirstVisiblePosition());
- setPressedItem(child, position, x, y);
- handledEvent = true;
-
- if (actionMasked == MotionEvent.ACTION_UP) {
- clickPressedItem(child, position);
- }
- break;
- }
-
- // Failure to handle the event cancels forwarding.
- if (!handledEvent || clearPressedItem) {
- clearPressedItem();
- }
-
- // Manage automatic scrolling.
- if (handledEvent) {
- if (mScrollHelper == null) {
- mScrollHelper = new AbsListViewAutoScroller(this);
- }
- mScrollHelper.setEnabled(true);
- mScrollHelper.onTouch(this, event);
- } else if (mScrollHelper != null) {
- mScrollHelper.setEnabled(false);
- }
-
- return handledEvent;
- }
-
- /**
- * Starts an alpha animation on the selector. When the animation ends,
- * the list performs a click on the item.
- */
- private void clickPressedItem(final View child, final int position) {
- final long id = getItemIdAtPosition(position);
- final Animator anim = ObjectAnimator.ofInt(
- mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF);
- anim.setDuration(CLICK_ANIM_DURATION);
- anim.setInterpolator(new AccelerateDecelerateInterpolator());
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- performItemClick(child, position, id);
- }
- });
- anim.start();
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- }
- mClickAnimation = anim;
- }
-
- private void clearPressedItem() {
- mDrawsInPressedState = false;
- setPressed(false);
- updateSelectorState();
-
- final View motionView = getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null) {
- motionView.setPressed(false);
- }
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- mClickAnimation = null;
- }
- }
-
- private void setPressedItem(View child, int position, float x, float y) {
- mDrawsInPressedState = true;
-
- // Ordering is essential. First, update the container's pressed state.
- drawableHotspotChanged(x, y);
- if (!isPressed()) {
- setPressed(true);
- }
-
- // Next, run layout if we need to stabilize child positions.
- if (mDataChanged) {
- layoutChildren();
- }
-
- // Manage the pressed view based on motion position. This allows us to
- // play nicely with actual touch and scroll events.
- final View motionView = getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null && motionView != child && motionView.isPressed()) {
- motionView.setPressed(false);
- }
- mMotionPosition = position;
-
- // Offset for child coordinates.
- final float childX = x - child.getLeft();
- final float childY = y - child.getTop();
- child.drawableHotspotChanged(childX, childY);
- if (!child.isPressed()) {
- child.setPressed(true);
- }
-
- // Ensure that keyboard focus starts from the last touched position.
- setSelectedPositionInt(position);
- positionSelectorLikeTouch(position, child, x, y);
-
- // Refresh the drawable state to reflect the new pressed state,
- // which will also update the selector state.
- refreshDrawableState();
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- mClickAnimation = null;
- }
- }
-
- @Override
- boolean touchModeDrawsInPressedState() {
- return mDrawsInPressedState || super.touchModeDrawsInPressedState();
- }
-
- /**
- * <p>Avoids jarring scrolling effect by ensuring that list elements
- * made of a text view fit on a single line.</p>
- *
- * @param position the item index in the list to get a view for
- * @return the view for the specified item
- */
- @Override
- View obtainView(int position, boolean[] isScrap) {
- View view = super.obtainView(position, isScrap);
-
- if (view instanceof TextView) {
- ((TextView) view).setHorizontallyScrolling(true);
- }
-
- return view;
- }
-
- @Override
- public boolean isInTouchMode() {
- // WARNING: Please read the comment where mListSelectionHidden is declared
- return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean hasWindowFocus() {
- return mHijackFocus || super.hasWindowFocus();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean isFocused() {
- return mHijackFocus || super.isFocused();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean hasFocus() {
- return mHijackFocus || super.hasFocus();
- }
- }
-
private class PopupDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
diff --git a/core/java/android/widget/MenuPopupWindow.java b/core/java/android/widget/MenuPopupWindow.java
index 8d42c739b627..9e47e8513d49 100644
--- a/core/java/android/widget/MenuPopupWindow.java
+++ b/core/java/android/widget/MenuPopupWindow.java
@@ -36,11 +36,11 @@ public class MenuPopupWindow extends ListPopupWindow {
}
@Override
- ListPopupWindow.DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+ DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
return new MenuDropDownListView(context, hijackFocus);
}
- static class MenuDropDownListView extends ListPopupWindow.DropDownListView {
+ static class MenuDropDownListView extends DropDownListView {
private boolean mHoveredOnDisabledItem = false;
private AccessibilityManager mAccessibilityManager;
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 1507dfb29285..3b2d60d1e1a4 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -20,6 +20,7 @@ import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.ShowableListMenu;
import com.android.internal.view.menu.SubMenuBuilder;
import android.annotation.MenuRes;
@@ -30,7 +31,6 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnTouchListener;
-import android.widget.ListPopupWindow.ForwardingListener;
/**
* A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
@@ -170,7 +170,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
// This will be null until show() is called.
return mPopup.getPopup();
}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index f3cf61c7a1eb..c79e18449e39 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -17,6 +17,7 @@
package android.widget;
import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
import android.annotation.DrawableRes;
import android.annotation.Nullable;
@@ -44,7 +45,6 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ListPopupWindow.ForwardingListener;
import android.widget.PopupWindow.OnDismissListener;
/**
@@ -278,7 +278,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
mPopup = popup;
mForwardingListener = new ForwardingListener(this) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return popup;
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 8db363dadfa9..ce5bc908814c 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -29,10 +29,10 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ActionMenuView;
+import android.widget.ForwardingListener;
import android.widget.ListPopupWindow;
import android.widget.TextView;
import android.widget.Toast;
-import android.widget.ListPopupWindow.ForwardingListener;
/**
* @hide
@@ -320,7 +320,7 @@ public class ActionMenuItemView extends TextView
}
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
if (mPopupCallback != null) {
return mPopupCallback.getPopup();
}
@@ -331,7 +331,7 @@ public class ActionMenuItemView extends TextView
protected boolean onForwardingStarted() {
// Call the invoker, then check if the expected popup is showing.
if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
- final ListPopupWindow popup = getPopup();
+ final ShowableListMenu popup = getPopup();
return popup != null && popup.isShowing();
}
return false;
@@ -339,7 +339,7 @@ public class ActionMenuItemView extends TextView
@Override
protected boolean onForwardingStopped() {
- final ListPopupWindow popup = getPopup();
+ final ShowableListMenu popup = getPopup();
if (popup != null) {
popup.dismiss();
return true;
@@ -349,6 +349,6 @@ public class ActionMenuItemView extends TextView
}
public static abstract class PopupCallback {
- public abstract ListPopupWindow getPopup();
+ public abstract ShowableListMenu getPopup();
}
}
diff --git a/core/java/com/android/internal/view/menu/MenuAdapter.java b/core/java/com/android/internal/view/menu/MenuAdapter.java
new file mode 100644
index 000000000000..1e03b1f8b971
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuAdapter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 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 com.android.internal.view.menu;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+public class MenuAdapter extends BaseAdapter {
+ static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
+
+ MenuBuilder mAdapterMenu;
+
+ private int mExpandedIndex = -1;
+
+ private boolean mForceShowIcon;
+ private final boolean mOverflowOnly;
+ private final LayoutInflater mInflater;
+
+ public MenuAdapter(MenuBuilder menu, LayoutInflater inflater, boolean overflowOnly) {
+ mOverflowOnly = overflowOnly;
+ mInflater = inflater;
+ mAdapterMenu = menu;
+ findExpandedIndex();
+ }
+
+ public void setForceShowIcon(boolean forceShow) {
+ mForceShowIcon = forceShow;
+ }
+
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex < 0) {
+ return items.size();
+ }
+ return items.size() - 1;
+ }
+
+ public MenuBuilder getAdapterMenu() {
+ return mAdapterMenu;
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+ position++;
+ }
+ return items.get(position);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ if (mForceShowIcon) {
+ ((ListMenuItemView) convertView).setForceShowIcon(true);
+ }
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+
+ void findExpandedIndex() {
+ final MenuItemImpl expandedItem = mAdapterMenu.getExpandedItem();
+ if (expandedItem != null) {
+ final ArrayList<MenuItemImpl> items = mAdapterMenu.getNonActionItems();
+ final int count = items.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItemImpl item = items.get(i);
+ if (item == expandedItem) {
+ mExpandedIndex = i;
+ return;
+ }
+ }
+ }
+ mExpandedIndex = -1;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ findExpandedIndex();
+ super.notifyDataSetChanged();
+ }
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 13654a695fcf..e6bc6c3ae044 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -89,7 +89,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
mContext = context;
mInflater = LayoutInflater.from(context);
mMenu = menu;
- mAdapter = new MenuAdapter(mMenu);
+ mAdapter = new MenuAdapter(mMenu, mInflater, overflowOnly);
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
@@ -358,73 +358,4 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
@Override
public void onRestoreInstanceState(Parcelable state) {
}
-
- private class MenuAdapter extends BaseAdapter {
- private MenuBuilder mAdapterMenu;
- private int mExpandedIndex = -1;
-
- public MenuAdapter(MenuBuilder menu) {
- mAdapterMenu = menu;
- findExpandedIndex();
- }
-
- public int getCount() {
- ArrayList<MenuItemImpl> items = mOverflowOnly ?
- mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
- if (mExpandedIndex < 0) {
- return items.size();
- }
- return items.size() - 1;
- }
-
- public MenuItemImpl getItem(int position) {
- ArrayList<MenuItemImpl> items = mOverflowOnly ?
- mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
- if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
- position++;
- }
- return items.get(position);
- }
-
- public long getItemId(int position) {
- // Since a menu item's ID is optional, we'll use the position as an
- // ID for the item in the AdapterView
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
- }
-
- MenuView.ItemView itemView = (MenuView.ItemView) convertView;
- if (mForceShowIcon) {
- ((ListMenuItemView) convertView).setForceShowIcon(true);
- }
- itemView.initialize(getItem(position), 0);
- return convertView;
- }
-
- void findExpandedIndex() {
- final MenuItemImpl expandedItem = mMenu.getExpandedItem();
- if (expandedItem != null) {
- final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
- final int count = items.size();
- for (int i = 0; i < count; i++) {
- final MenuItemImpl item = items.get(i);
- if (item == expandedItem) {
- mExpandedIndex = i;
- return;
- }
- }
- }
- mExpandedIndex = -1;
- }
-
- @Override
- public void notifyDataSetChanged() {
- findExpandedIndex();
- super.notifyDataSetChanged();
- }
- }
}
diff --git a/core/java/com/android/internal/view/menu/ShowableListMenu.java b/core/java/com/android/internal/view/menu/ShowableListMenu.java
new file mode 100644
index 000000000000..ca158fdb690e
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ShowableListMenu.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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 com.android.internal.view.menu;
+
+import android.widget.ListView;
+
+/**
+ * A list menu which can be shown and hidden and which is internally represented by a ListView.
+ */
+public interface ShowableListMenu {
+ public void show();
+
+ public void dismiss();
+
+ public boolean isShowing();
+
+ /**
+ * @return The internal ListView for the visible menu.
+ */
+ public ListView getListView();
+}