summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorAlan Viverette <alanv@google.com>2015-03-11 21:04:24 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-03-11 21:04:28 +0000
commit1130363f18bed5206e3184c2d0c0a368d8268c66 (patch)
treed1cfdeccb7902a8aeced019d7fb4d250aca55c04 /core
parent31f2b619fb1a2b63a20725412179afcc5f04f998 (diff)
parent8fd949e680c15d397084430d4907c16cedfacdda (diff)
downloadframeworks_base-1130363f18bed5206e3184c2d0c0a368d8268c66.zip
frameworks_base-1130363f18bed5206e3184c2d0c0a368d8268c66.tar.gz
frameworks_base-1130363f18bed5206e3184c2d0c0a368d8268c66.tar.bz2
Merge "Various fixes for popup monkey testing"
Diffstat (limited to 'core')
-rw-r--r--core/java/android/widget/PopupWindow.java252
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java15
2 files changed, 165 insertions, 102 deletions
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 399f4c5..f676254 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -29,6 +29,8 @@ import android.os.Build;
import android.os.IBinder;
import android.transition.Transition;
import android.transition.Transition.EpicenterCallback;
+import android.transition.Transition.TransitionListener;
+import android.transition.Transition.TransitionListenerAdapter;
import android.transition.TransitionInflater;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
@@ -39,12 +41,13 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowManager;
import java.lang.ref.WeakReference;
-import java.util.List;
/**
* <p>A popup window that can be used to display an arbitrary view. The popup
@@ -96,14 +99,12 @@ public class PopupWindow {
private WindowManager mWindowManager;
private boolean mIsShowing;
+ private boolean mIsTransitioningToDismiss;
private boolean mIsDropdown;
/** View that handles event dispatch and content transitions. */
private PopupDecorView mDecorView;
- /** View that holds the popup background. May be the content view. */
- private View mBackgroundView;
-
/** The contents of the popup. */
private View mContentView;
@@ -1183,23 +1184,30 @@ public class PopupWindow {
+ "calling setContentView() before attempting to show the popup.");
}
+ // The old decor view may be transitioning out. Make sure it finishes
+ // and cleans up before we try to create another one.
+ if (mDecorView != null) {
+ mDecorView.cancelTransitions();
+ }
+
// When a background is available, we embed the content view within
// another view that owns the background drawable.
+ final View backgroundView;
if (mBackground != null) {
- mBackgroundView = createBackgroundView(mContentView);
- mBackgroundView.setBackground(mBackground);
+ backgroundView = createBackgroundView(mContentView);
+ backgroundView.setBackground(mBackground);
} else {
- mBackgroundView = mContentView;
+ backgroundView = mContentView;
}
- mDecorView = createDecorView(mBackgroundView);
+ mDecorView = createDecorView(backgroundView);
// The background owner should be elevated so that it casts a shadow.
- mBackgroundView.setElevation(mElevation);
+ backgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
- final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
+ final int surfaceInset = (int) Math.ceil(backgroundView.getZ() * 2);
p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
p.hasManualSurfaceInsets = true;
@@ -1268,26 +1276,13 @@ public class PopupWindow {
p.packageName = mContext.getPackageName();
}
- final View rootView = mContentView.getRootView();
- rootView.setFitsSystemWindows(mLayoutInsetDecor);
- setLayoutDirectionFromAnchor();
-
- mWindowManager.addView(rootView, p);
+ final PopupDecorView decorView = mDecorView;
+ decorView.setFitsSystemWindows(mLayoutInsetDecor);
+ decorView.requestEnterTransition(mEnterTransition);
- // Postpone enter transition until the scene root has been laid out.
- if (mEnterTransition != null) {
- mEnterTransition.addTarget(mBackgroundView);
- mEnterTransition.addListener(new Transition.TransitionListenerAdapter() {
- @Override
- public void onTransitionEnd(Transition transition) {
- transition.removeListener(this);
- transition.removeTarget(mBackgroundView);
- }
- });
+ setLayoutDirectionFromAnchor();
- mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(
- new PostLayoutTransitionListener(mDecorView, mEnterTransition));
- }
+ mWindowManager.addView(decorView, p);
}
private void setLayoutDirectionFromAnchor() {
@@ -1591,35 +1586,38 @@ public class PopupWindow {
* @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
- if (!isShowing()) {
+ if (!isShowing() || mIsTransitioningToDismiss) {
return;
}
+ final PopupDecorView decorView = mDecorView;
+ final View contentView = mContentView;
+
+ final ViewGroup contentHolder;
+ final ViewParent contentParent = contentView.getParent();
+ if (contentParent instanceof ViewGroup) {
+ contentHolder = ((ViewGroup) contentParent);
+ } else {
+ contentHolder = null;
+ }
+
+ // Ensure any ongoing or pending transitions are canceled.
+ decorView.cancelTransitions();
+
unregisterForScrollChanged();
mIsShowing = false;
+ mIsTransitioningToDismiss = true;
- if (mExitTransition != null) {
- // Cache the content view, since it may change without notice.
- final View contentView = mContentView;
-
- mExitTransition.addTarget(mBackgroundView);
- mExitTransition.addListener(new Transition.TransitionListenerAdapter() {
+ if (mExitTransition != null && decorView.isLaidOut()) {
+ decorView.startExitTransition(mExitTransition, new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
- transition.removeListener(this);
- transition.removeTarget(mBackgroundView);
-
- dismissImmediate(contentView);
+ dismissImmediate(decorView, contentHolder, contentView);
}
});
-
- TransitionManager.beginDelayedTransition(mDecorView, mExitTransition);
-
- // Transition to invisible.
- mBackgroundView.setVisibility(View.INVISIBLE);
} else {
- dismissImmediate(mContentView);
+ dismissImmediate(decorView, contentHolder, contentView);
}
if (mOnDismissListener != null) {
@@ -1631,24 +1629,22 @@ public class PopupWindow {
* Removes the popup from the window manager and tears down the supporting
* view hierarchy, if necessary.
*/
- private void dismissImmediate(View contentView) {
- if (mDecorView == null || mBackgroundView == null) {
- throw new RuntimeException("Popup window already dismissed");
+ private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
+ // If this method gets called and the decor view doesn't have a parent,
+ // then it was either never added or was already removed. That should
+ // never happen, but it's worth checking to avoid potential crashes.
+ if (decorView.getParent() != null) {
+ mWindowManager.removeViewImmediate(decorView);
}
- try {
- if (mDecorView.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(mDecorView);
- }
- } finally {
- mDecorView.removeView(mBackgroundView);
- mDecorView = null;
-
- if (mBackgroundView != contentView) {
- ((ViewGroup) mBackgroundView).removeView(contentView);
- }
- mBackgroundView = null;
+ if (contentHolder != null) {
+ contentHolder.removeView(contentView);
}
+
+ // This needs to stay until after all transitions have ended since we
+ // need the reference to cancel transitions in preparePopup().
+ mDecorView = null;
+ mIsTransitioningToDismiss = false;
}
/**
@@ -1909,47 +1905,9 @@ public class PopupWindow {
mAnchoredGravity = gravity;
}
- /**
- * Layout listener used to run a transition immediately after a view is
- * laid out. Forces the view to transition from invisible to visible.
- */
- private static class PostLayoutTransitionListener implements
- ViewTreeObserver.OnGlobalLayoutListener {
- private final ViewGroup mSceneRoot;
- private final Transition mTransition;
-
- public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) {
- mSceneRoot = sceneRoot;
- mTransition = transition;
- }
-
- @Override
- public void onGlobalLayout() {
- final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver();
- if (observer == null) {
- // View has been detached.
- return;
- }
-
- observer.removeOnGlobalLayoutListener(this);
-
- // Set all targets to be initially invisible.
- final List<View> targets = mTransition.getTargets();
- final int N = targets.size();
- for (int i = 0; i < N; i++) {
- targets.get(i).setVisibility(View.INVISIBLE);
- }
-
- TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
-
- // Transition targets to visible.
- for (int i = 0; i < N; i++) {
- targets.get(i).setVisibility(View.VISIBLE);
- }
- }
- }
-
private class PopupDecorView extends FrameLayout {
+ private TransitionListenerAdapter mPendingExitListener;
+
public PopupDecorView(Context context) {
super(context);
}
@@ -2004,6 +1962,100 @@ public class PopupWindow {
return super.onTouchEvent(event);
}
}
+
+ /**
+ * Requests that an enter transition run after the next layout pass.
+ */
+ public void requestEnterTransition(Transition transition) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null && transition != null) {
+ final Transition enterTransition = transition.clone();
+
+ // Postpone the enter transition after the first layout pass.
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null) {
+ observer.removeOnGlobalLayoutListener(this);
+ }
+
+ startEnterTransition(enterTransition);
+ }
+ });
+ }
+ }
+
+ /**
+ * Starts the pending enter transition, if one is set.
+ */
+ private void startEnterTransition(Transition enterTransition) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ enterTransition.addTarget(child);
+ child.setVisibility(View.INVISIBLE);
+ }
+
+ TransitionManager.beginDelayedTransition(this, enterTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Starts an exit transition immediately.
+ * <p>
+ * <strong>Note:</strong> The transition listener is guaranteed to have
+ * its {@code onTransitionEnd} method called even if the transition
+ * never starts; however, it may be called with a {@code null} argument.
+ */
+ public void startExitTransition(Transition transition, final TransitionListener listener) {
+ if (transition == null) {
+ return;
+ }
+
+ // The exit listener MUST be called for cleanup, even if the
+ // transition never starts or ends. Stash it for later.
+ mPendingExitListener = new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ listener.onTransitionEnd(transition);
+
+ // The listener was called. Our job here is done.
+ mPendingExitListener = null;
+ }
+ };
+
+ final Transition exitTransition = transition.clone();
+ exitTransition.addListener(mPendingExitListener);
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ exitTransition.addTarget(child);
+ }
+
+ TransitionManager.beginDelayedTransition(this, exitTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ /**
+ * Cancels all pending or current transitions.
+ */
+ public void cancelTransitions() {
+ TransitionManager.endTransitions(this);
+
+ if (mPendingExitListener != null) {
+ mPendingExitListener.onTransitionEnd(null);
+ }
+ }
}
private class PopupBackgroundView extends FrameLayout {
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 2b20b38..7d45071 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -43,8 +43,6 @@ import java.util.ArrayList;
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
View.OnAttachStateChangeListener, MenuPresenter {
- private static final String TAG = "MenuPopupHelper";
-
static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
private final Context mContext;
@@ -132,7 +130,18 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return mPopup;
}
+ /**
+ * Attempts to show the popup anchored to the view specified by
+ * {@link #setAnchorView(View)}.
+ *
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
public boolean tryShow() {
+ if (isShowing()) {
+ return true;
+ }
+
mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
mPopup.setOnDismissListener(this);
mPopup.setOnItemClickListener(this);
@@ -169,6 +178,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
}
}
+ @Override
public void onDismiss() {
mPopup = null;
mMenu.close();
@@ -190,6 +200,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
dismiss();