diff options
5 files changed, 104 insertions, 18 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index f6ac1cc84f43..10de4497c05c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -123,6 +123,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.Preconditions; +import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.EditableInputConnection; import java.lang.annotation.Retention; @@ -2215,6 +2216,15 @@ public class Editor { ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode); mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING); + final boolean selectableText = mTextView.isTextEditable() || mTextView.isTextSelectable(); + if (actionMode == TextActionMode.TEXT_LINK && !selectableText + && mTextActionMode instanceof FloatingActionMode) { + // Make the toolbar outside-touchable so that it can be dismissed when the user clicks + // outside of it. + ((FloatingActionMode) mTextActionMode).setOutsideTouchable(true, + () -> stopTextActionMode()); + } + final boolean selectionStarted = mTextActionMode != null; if (selectionStarted && mTextView.isTextEditable() && !mTextView.isTextSelectable() diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java index 497e7b08d881..54dede6753e4 100644 --- a/core/java/com/android/internal/view/FloatingActionMode.java +++ b/core/java/com/android/internal/view/FloatingActionMode.java @@ -17,6 +17,7 @@ package com.android.internal.view; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; @@ -30,6 +31,7 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.WindowManager; +import android.widget.PopupWindow; import com.android.internal.R; import com.android.internal.util.Preconditions; import com.android.internal.view.menu.MenuBuilder; @@ -241,6 +243,23 @@ public final class FloatingActionMode extends ActionMode { } } + /** + * If this is set to true, the action mode view will dismiss itself on touch events outside of + * its window. This only makes sense if the action mode view is a PopupWindow that is touchable + * but not focusable, which means touches outside of the window will be delivered to the window + * behind. The default is false. + * + * This is for internal use only and the approach to this may change. + * @hide + * + * @param outsideTouchable whether or not this action mode is "outside touchable" + * @param onDismiss optional. Sets a callback for when this action mode popup dismisses itself + */ + public void setOutsideTouchable( + boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { + mFloatingToolbar.setOutsideTouchable(outsideTouchable, onDismiss); + } + @Override public void onWindowFocusChanged(boolean hasWindowFocus) { mFloatingToolbarVisibilityHelper.setWindowFocused(hasWindowFocus); diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index f70c554d9bf0..42fb1f5c34e9 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; @@ -259,6 +260,22 @@ public final class FloatingToolbar { return mPopup.isHidden(); } + /** + * If this is set to true, the action mode view will dismiss itself on touch events outside of + * its window. If the toolbar is already showing, it will be re-shown so that this setting takes + * effect immediately. + * + * @param outsideTouchable whether or not this action mode is "outside touchable" + * @param onDismiss optional. Sets a callback for when this action mode popup dismisses itself + */ + public void setOutsideTouchable( + boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { + if (mPopup.setOutsideTouchable(outsideTouchable, onDismiss) && isShowing()) { + dismiss(); + doShow(); + } + } + private void doShow() { List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu); menuItems.sort(mMenuItemComparator); @@ -513,6 +530,32 @@ public final class FloatingToolbar { } /** + * Makes this toolbar "outside touchable" and sets the onDismissListener. + * This will take effect the next time the toolbar is re-shown. + * + * @param outsideTouchable if true, the popup will be made "outside touchable" and + * "non focusable". The reverse will happen if false. + * @param onDismiss + * + * @return true if the "outsideTouchable" setting was modified. Otherwise returns false + * + * @see PopupWindow#setOutsideTouchable(boolean) + * @see PopupWindow#setFocusable(boolean) + * @see PopupWindow.OnDismissListener + */ + public boolean setOutsideTouchable( + boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) { + boolean ret = false; + if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) { + mPopupWindow.setOutsideTouchable(outsideTouchable); + mPopupWindow.setFocusable(!outsideTouchable); + ret = true; + } + mPopupWindow.setOnDismissListener(onDismiss); + return ret; + } + + /** * Lays out buttons for the specified menu items. * Requires a subsequent call to {@link #show()} to show the items. */ diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml index d5be87dbae6b..056868392949 100644 --- a/core/tests/coretests/res/layout/activity_text_view.xml +++ b/core/tests/coretests/res/layout/activity_text_view.xml @@ -31,6 +31,8 @@ <TextView android:id="@+id/nonselectable_textview" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:focusable="false" + android:focusableInTouchMode="false" /> </LinearLayout> diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 320a7acdb47c..bcb7cf393f33 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -318,40 +318,52 @@ public class TextViewActivityTest { onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position)); sleepForFloatingToolbarPopup(); assertFloatingToolbarIsDisplayed(); - } @Test public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable { - TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); - int position = (textLink.getStart() + textLink.getEnd()) / 2; + final TextView textView = mActivity.findViewById(R.id.nonselectable_textview); + final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + final int position = (textLink.getStart() + textLink.getEnd()) / 2; + + onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + assertTrue(textView.hasSelection()); + + // toggle + onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); + sleepForFloatingToolbarPopup(); + assertFalse(textView.hasSelection()); + onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); sleepForFloatingToolbarPopup(); assertFloatingToolbarIsDisplayed(); + assertTrue(textView.hasSelection()); + + // click outside + onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(0)); + sleepForFloatingToolbarPopup(); + assertFalse(textView.hasSelection()); } @Test public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable { - // Add a link to both selectable and nonselectable TextViews: - TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview); - int selectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; - textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); - int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2; - TextView selectableTextView = mActivity.findViewById(R.id.textview); - TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview); + final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview); + final int position = (textLink.getStart() + textLink.getEnd()) / 2; + final TextView textView = mActivity.findViewById(R.id.nonselectable_textview); + mActivityRule.runOnUiThread(() -> textView.setFocusableInTouchMode(true)); - onView(withId(R.id.nonselectable_textview)) - .perform(clickOnTextAtIndex(nonselectablePosition)); + onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position)); sleepForFloatingToolbarPopup(); assertFloatingToolbarIsDisplayed(); - assertTrue(nonselectableTextView.hasSelection()); + assertTrue(textView.hasSelection()); - onView(withId(R.id.textview)).perform(clickOnTextAtIndex(selectablePosition)); + mActivityRule.runOnUiThread(() -> textView.clearFocus()); + mInstrumentation.waitForIdleSync(); sleepForFloatingToolbarPopup(); - assertFloatingToolbarIsDisplayed(); - assertTrue(selectableTextView.hasSelection()); - assertFalse(nonselectableTextView.hasSelection()); + assertFalse(textView.hasSelection()); } @Test |