diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 81643e90428..5054c82e9f1 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1135,6 +1135,36 @@ public class TextUtils {
         return offset;
     }
 
+    /** @hide */
+    public static int getOffsetStartOf(CharSequence text, int offset) {
+        if (offset == 0 || offset == text.length())
+            return offset;
+
+        char c = text.charAt(offset);
+
+        if (c >= '\uDC00' && c <= '\uDFFF') {
+            char c1 = text.charAt(offset - 1);
+
+            if (c1 >= '\uD800' && c1 <= '\uDBFF')
+                offset -= 1;
+        }
+
+        if (text instanceof Spanned) {
+            ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
+                    ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int start = ((Spanned) text).getSpanStart(spans[i]);
+                int end = ((Spanned) text).getSpanEnd(spans[i]);
+
+                if (start < offset && end > offset)
+                    offset = start;
+            }
+        }
+
+        return offset;
+    }
+
     private static void readSpan(Parcel p, Spannable sp, Object o) {
         sp.setSpan(o, p.readInt(), p.readInt(), p.readInt());
     }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index f34f9e6d5ce..ca0ba36faf7 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -123,4 +123,6 @@ oneway interface IWindow {
      * Tell the window that it is either gaining or losing pointer capture.
      */
     void dispatchPointerCaptureChanged(boolean hasCapture);
+
+    void getTextContent(float x, float y);
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f6edb2bf315..9c9ce0cdc8a 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -648,4 +648,6 @@ interface IWindowManager
     void hideShareScreenSurface();
 
     void drawShareScreenBitMap(in Bitmap bg);
+    
+    void getTextContent(int displayId, float x, float y);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5576de354c7..fe4e9444b11 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -29562,6 +29576,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
         stream.addProperty("accessibility:importantForAccessibility", getImportantForAccessibility());
     }
 
+    /** @hide */
+    public View dispatchFindView(float x, float y) {
+        return null;
+    }
+
+    /** @hide */
+    public void setFindText(String str) {
+        mFindText = str;
+    }
+
+    /** @hide */
+    public String getFindText() {
+        return mFindText;
+    }
+
+    /** @hide */
+    public int getFindTextIndex() {
+        return mFindTextIndex;
+    }
+
+    /** @hide */
+    public void setFindTextIndex(int index) {
+        mFindTextIndex = index;
+    }
+
+    private String mFindText;
+    private int mFindTextIndex = -1;
+
     /**
      * Determine if this view is rendered on a round wearable device and is the main view
      * on the screen.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 937bd1b34e6..164c0153f87 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -72,7 +72,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
-
+import android.webkit.WebView;
 /**
  * <p>
  * A <code>ViewGroup</code> is a special view that can contain other views
@@ -8993,4 +8993,48 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
             getChildAt(i).encode(encoder);
         }
     }
+
+        /**
+     * @hide
+     */
+    public View dispatchFindView(float x, float y) {
+        final int childrenCount = mChildrenCount;
+        if (childrenCount == 0 || getVisibility() != View.VISIBLE)
+            return null;
+
+        // Find a child that can receive the event.
+        // Scan children from front to back.
+        final ArrayList<View> preorderedList = buildOrderedChildList();
+        final boolean customOrder = preorderedList == null
+                && isChildrenDrawingOrderEnabled();
+        final View[] children = mChildren;
+        for (int i = childrenCount - 1; i >= 0; i--) {
+            final int childIndex = customOrder
+                    ? getChildDrawingOrder(childrenCount, i) : i;
+            final View child = (preorderedList == null)
+                    ? children[childIndex] : preorderedList.get(childIndex);
+            if (child.getVisibility() != View.VISIBLE) {
+                continue;
+            }
+            if (isTransformedTouchPointInView(x, y, child, null)) {
+                final float offsetX = mScrollX - child.mLeft;
+                final float offsetY = mScrollY - child.mTop;
+                float newX = x + offsetX;
+                float newY = y + offsetY;
+                View ret = child.dispatchFindView(newX, newY);
+                if (ret != null) {
+                    if (preorderedList != null) preorderedList.clear();
+                    return ret;
+                } else if (child instanceof ViewGroup) {
+                    if (child instanceof WebView
+                            || child.getClass().getName().startsWith("org.chromium.content.browser.ContentView")) {
+                        if (preorderedList != null) preorderedList.clear();
+                        return child;
+                    }
+                }
+            }
+        }
+        if (preorderedList != null) preorderedList.clear();
+        return null;
+    }
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c48e4e9b97e..a9a258f3f28 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -112,7 +112,7 @@ import android.view.contentcapture.ContentCaptureSession;
 import android.view.contentcapture.MainContentCaptureSession;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Scroller;
-
+import android.widget.TextExtractor;
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.IResultReceiver;
@@ -590,6 +590,8 @@ public final class ViewRootImpl implements ViewParent,
 
     private final InputEventCompatProcessor mInputCompatProcessor;
 
+    private TextExtractor mTextExtractor;
+
     /**
      * Consistency verifier for debugging purposes.
      */
@@ -8035,6 +8037,18 @@ public final class ViewRootImpl implements ViewParent,
         mHandler.sendMessage(msg);
     }
 
+    public void getTextContent(float x, float y) {
+        synchronized (this) {
+            if (mTextExtractor != null) {
+                mTextExtractor.getTextContent(x, y);
+            }
+        }
+    }
+
+    public void setTextExtractor(TextExtractor textExtractor) {
+        mTextExtractor = textExtractor;
+    }
+
     public void dispatchWindowShown() {
         mHandler.sendEmptyMessage(MSG_DISPATCH_WINDOW_SHOWN);
     }
@@ -8607,6 +8621,14 @@ public final class ViewRootImpl implements ViewParent,
             }
         }
 
+        @Override
+        public void getTextContent(float x, float y) {
+            final ViewRootImpl viewAncestor = mViewAncestor.get();
+            if (viewAncestor != null) {
+                viewAncestor.getTextContent(x, y);
+            }
+        }
+
         private static int checkCallingPermission(String permission) {
             try {
                 return ActivityManager.getService().checkPermission(
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index a557bd1e191..17831d2f82f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -418,6 +418,8 @@ public interface WindowManager extends ViewManager {
 
+    public void getTextContent(int displayId, float x, float y);
+
     /**
      * Special variation of {@link #removeView} that immediately invokes
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5c69c5f2185..a17c4a8550e 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -260,5 +260,14 @@ public final class WindowManagerImpl implements WindowManager {
         } catch (RemoteException e) {
         }
     }
+
+    @Override
+    public void getTextContent(int displayId, float x, float y) {
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                   .getTextContent(displayId, x, y);
+        } catch (RemoteException e) {
+        }
+    }
     // endregion
 }

diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 634cbe323d8..523bab827be 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -19,6 +19,7 @@ package android.widget;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.widget.RemoteViews.RemoteView;
@@ -171,6 +172,12 @@ public class Button extends TextView {
         return Button.class.getName();
     }
 
+    /** @hide */
+    @Override
+    public View dispatchFindView(float x, float y) {
+        return null;
+    }
+
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
         if (getPointerIcon() == null && isClickable() && isEnabled()) {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index be5d2211c67..92e3104bc0c 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -1717,6 +1717,12 @@ public class ImageView extends View {
         stream.addProperty("layout:baseline", getBaseline());
     }
 
+    /** @hide */
+    @Override
+    public View dispatchFindView(float x, float y) {
+        return null;
+    }
+
     /** @hide */
     @Override
     @TestApi
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 36fdd1c7f7f..ead3e554dd9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -212,6 +212,8 @@ import java.util.function.Supplier;
 import android.app.ActivityThread;
 import android.widget.FrameLayout;
 import android.app.Application;
+import android.util.Patterns;
+import java.util.regex.Matcher;
 
 /**
  * A user interface element that displays text to the user.
@@ -839,6 +841,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     // by removing it, but we would break apps targeting <= P that use it by reflection.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     int mTextSelectHandleLeftRes;
+    private final int mMaxFindTextLength;
     private Drawable mTextSelectHandleLeft;
     // Note: this might be stale if setTextSelectHandleRight is used. We could simplify the code
     // by removing it, but we would break apps targeting <= P that use it by reflection.
@@ -994,6 +997,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
 
         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
         mTextPaint.density = res.getDisplayMetrics().density;
+        mMaxFindTextLength = 1000;
         mTextPaint.setCompatibilityScaling(compat.applicationScale);
 
         mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -13471,4 +13475,112 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
             TextView.this.spanChange(buf, what, s, -1, e, -1);
         }
     }
+
+    private final static int EXTEND_WORD_OFFSET = 10;
+    private final static char NEW_LINE = '\n';
+
+    /** @hide */
+    @Override
+    public View dispatchFindView(float x, float y) {
+        String foundText = null;
+        int offset = getOffsetForPosition(x, y);
+        final int length = mText.length();
+        if (offset == length) offset = length - 1;
+        if (offset != -1) {
+            String stringText = mText.toString();
+            int start = stringText.lastIndexOf(NEW_LINE, offset - 1);
+            int end = stringText.indexOf(NEW_LINE, offset + 1);
+            if (start == -1 && end == -1) {
+                if (mMaxFindTextLength >= length) {
+                    start = 0;
+                    end = length;
+                } else {
+                    boolean getBefore = offset >= mMaxFindTextLength / 2;
+                    boolean getAfter = length - offset >= mMaxFindTextLength / 2;
+                    if (getBefore && getAfter) {
+                        start = TextUtils.getOffsetStartOf(mText, offset - mMaxFindTextLength / 2);
+                        end = TextUtils.getOffsetStartOf(mText, offset + mMaxFindTextLength / 2);
+                    } else if (getBefore) {
+                        start = TextUtils.getOffsetStartOf(mText, length - mMaxFindTextLength);
+                        end = length;
+                    } else {
+                        start = 0;
+                        end = TextUtils.getOffsetStartOf(mText, mMaxFindTextLength);
+                    }
+                }
+            } else if (start == -1) {
+                start = TextUtils.getOffsetStartOf(mText, Math.max(0, end - mMaxFindTextLength));
+            } else if (end == -1) {
+                end = TextUtils.getOffsetStartOf(mText, Math.min(length, start + mMaxFindTextLength));
+            } else {
+                if (end - start > mMaxFindTextLength) {
+                    boolean getBefore = offset - start >= mMaxFindTextLength / 2;
+                    boolean getAfter = end - offset >= mMaxFindTextLength / 2;
+                    if (getBefore && getAfter) {
+                        start = TextUtils.getOffsetStartOf(mText, offset - mMaxFindTextLength / 2);
+                        end = TextUtils.getOffsetStartOf(mText, offset + mMaxFindTextLength / 2);
+                    } else if (getBefore) {
+                        start = TextUtils.getOffsetStartOf(mText, end - mMaxFindTextLength);
+                    } else {
+                        end = TextUtils.getOffsetStartOf(mText, start + mMaxFindTextLength);
+                    }
+                }
+            }
+            offset -= start;
+            if (Character.isLetter(mText.charAt(start))) {
+                int i, count = 0;
+                for (i = start - 1; i >= 0 && count < EXTEND_WORD_OFFSET; --i) {
+                    if (Character.isLetter(mText.charAt(i))) {
+                        ++count;
+                    } else {
+                        break;
+                    }
+                }
+                if (i < 0 || count < EXTEND_WORD_OFFSET) {
+                    start -= count;
+                    offset += count;
+                }
+            }
+            if (Character.isLetter(mText.charAt(end - 1))) {
+                int i, count = 0;
+                for (i = end; i < length && count < EXTEND_WORD_OFFSET; ++i) {
+                    if (Character.isLetter(mText.charAt(i))) {
+                        ++count;
+                    } else {
+                        break;
+                    }
+                }
+                if (i == length || count < EXTEND_WORD_OFFSET) {
+                    end += count;
+                }
+            }
+            Matcher m = Patterns.WEB_URL.matcher(mText);
+            while (m.find()) {
+                final int ps = m.start();
+                final int pe = m.end();
+                if (Linkify.sUrlMatchFilter.acceptMatch(mText, ps, pe)) {
+                    if (pe > start && ps < end) {
+                        final int newStart = Math.min(ps, start);
+                        final int newEnd = Math.max(pe, end);
+                        offset += start - newStart;
+                        start = newStart;
+                        end = newEnd;
+                    }
+                }
+            }
+            foundText = mText.subSequence(start, end).toString();
+        } else if (length > 0) {
+            foundText = mText.subSequence(0, Math.min(length, mMaxFindTextLength)).toString();
+        }
+        mTrimmedFoundText = foundText;
+        setFindTextIndex(offset);
+        setFindText(mText.toString());
+        return this;
+    }
+
+    private String mTrimmedFoundText;
+    /** @hide */
+    public String getTrimmedFoundText() {
+        return mTrimmedFoundText;
+    }
 }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index fa83bd584fd..8a435ae323a 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -113,6 +113,7 @@ import com.android.internal.widget.DecorCaptionView;
 import com.android.internal.widget.FloatingToolbar;
 
 import java.util.List;
+import android.widget.TextExtractor;
 
 /** @hide */
 public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@@ -230,6 +231,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
 
     private int mRootScrollY = 0;
 
+    private final TextExtractor mTextExtractor;
     @UnsupportedAppUsage
     private PhoneWindow mWindow;
 
@@ -286,6 +288,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
         mSemiTransparentBarColor = context.getResources().getColor(
                 R.color.system_bar_background_semi_transparent, null /* theme */);
 
+        mTextExtractor = new TextExtractor(context, this);
         updateAvailableWidth();
 
         setWindow(window);
@@ -446,8 +449,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
 
     public boolean superDispatchKeyEvent(KeyEvent event) {
         // Give priority to closing action modes if applicable.
+        final int action = event.getAction();
         if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-            final int action = event.getAction();
             // Back cancels action modes first.
             if (mPrimaryActionMode != null) {
                 if (action == KeyEvent.ACTION_UP) {
@@ -461,6 +464,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
             return true;
         }
 
+        if (action == KeyEvent.ACTION_UP) {
+            mTextExtractor.handleBackKey();
+        }
+
         return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
     }
 
@@ -1739,6 +1746,13 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
             // renderer about it.
             mBackdropFrameRenderer.onConfigurationChange();
         }
+        if (mTextExtractor != null) {
+            mTextExtractor.onAttached(mWindow.getAttributes().type);
+        }
+        ViewRootImpl vri = getViewRootImpl();
+        if (vri != null && mTextExtractor != null) {
+            vri.setTextExtractor(mTextExtractor);
+        }
         mWindow.onViewRootImplSet(getViewRootImpl());
     }
 
@@ -1766,7 +1780,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
             mFloatingToolbar.dismiss();
             mFloatingToolbar = null;
         }
-
+        mTextExtractor.onDetached();
         PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
         if (st != null && st.menu != null && mFeatureId < 0) {
             st.menu.close();
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index f9cdf3d0be6..e67687c58dd 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -145,4 +145,8 @@ public class BaseIWindow extends IWindow.Stub {
     @Override
     public void dispatchPointerCaptureChanged(boolean hasCapture) {
     }
+
+    @Override
+    public void getTextContent(float x, float y) {
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 884c39c2427..942d094288a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3135,6 +3135,19 @@ public class WindowManagerService extends IWindowManager.Stub
         }
     }
 
+    @Override
+    public void getTextContent(int displayId, float x, float y) {
+        synchronized (mGlobalLock) {
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent != null) {
+                final WindowState touchedWin = displayContent.getTouchableWinAtPointLocked(x, y);
+                if (touchedWin != null) {
+                    touchedWin.getTextContent(x, y);
+                }
+            }
+        }
+    }
+
     @Override
     public void drawShareScreenBitMap(Bitmap bg) {
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4fb8a6c8d28..e487f60e1f5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2420,6 +2420,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
         if (614 == requestedWidth && "cn.guancha.app".equals(mAttrs.packageName)) {
             mWmService.mPendingRemove.add(this);
         }
    }
+
+    public void getTextContent(float x, float y) {
+        try {
+            mClient.getTextContent(x, y);
+        } catch (RemoteException e) {
+            // Not a remote call, RemoteException won't be raised.
+        }
     }
 
     void prepareWindowToDisplayDuringRelayout(boolean wasVisible) {

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐