summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/widget/Editor.java10
-rw-r--r--core/java/com/android/internal/view/FloatingActionMode.java19
-rw-r--r--core/java/com/android/internal/widget/FloatingToolbar.java43
-rw-r--r--core/tests/coretests/res/layout/activity_text_view.xml4
-rw-r--r--core/tests/coretests/src/android/widget/TextViewActivityTest.java46
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