如何使用可访问性服务从浏览器获取 url

9 android accessibility accessibilityservice android-accessibility

我已启用可访问性服务权限,现在我想从地址栏中获取 url。

我试过下面的事情:

Accessibility_service_config.xml

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description"
    android:notificationTimeout="0"
    android:canRequestFilterKeyEvents="true"
    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity" />
Run Code Online (Sandbox Code Playgroud)

访问服务.java

public class AccessService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        AccessibilityNodeInfo source = event.getSource();
        if (source == null)
            return;
        final String packageName = String.valueOf(source.getPackageName());
        String BROWSER_LIST = "com.android.chrome";
        List<String> browserList
                = Arrays.asList(BROWSER_LIST.split(",\\s*"));
        if (event.getEventType()
                == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
            if (!browserList.contains(packageName)) {
                return;
            }
        }

        if (browserList.contains(packageName)) {
            try {
                if (AccessibilityEvent
                        .eventTypeToString(event.getEventType())
                        .contains("WINDOW")) {
                    AccessibilityNodeInfo nodeInfo = event.getSource();
                    getUrlsFromViews(nodeInfo);
                }
            } catch (StackOverflowError ex) {
                ex.printStackTrace();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public void getUrlsFromViews(AccessibilityNodeInfo info) {

        try {
            if (info == null)
                return;
            if (info.getText() != null && info.getText().length() > 0) {
                String capturedText = info.getText().toString();
                Bundle arguments = new Bundle();
                if (capturedText.contains("https://")
                        || capturedText.contains("http://")) {

                   if (capturedText.contains("facebook.com")) {
                     // open new tab
                  }
                }
            }
            for (int i = 0; i < info.getChildCount(); i++) {
                AccessibilityNodeInfo child = info.getChild(i);
                getUrlsFromViews(child);
                if (child != null) {
                    child.recycle();
                }
            }
        } catch (StackOverflowError ex) {
            ex.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public void onInterrupt() {

    }

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
    }
}
Run Code Online (Sandbox Code Playgroud)

我在这里面临的问题是,当我在地址栏中输入 facebook.com 并点击 url 时,我只能得到facebook.com或者m.facebook.com,因此我无法采取任何行动。

我只想在地址栏中点击后获取 URL。另外我想打开新标签并关闭现有标签,当它在地址栏中显示 facebook.com 时。

有没有合适的方法来做到这一点?

Bel*_*loo 6

我在这里要补充一些内容以及我们如何改进您的解决方案。如果您接受以下限制可能会更好:

  • 该应用程序具有支持的浏览器的嵌入式列表。不支持任何其他浏览器。
  • 可访问性服务将查找地址栏文本字段的id,并尝试从那里拦截 URL。无法直接捕获要加载的 URL。为了找到这个 id,我们应该对目标浏览器进行一些逆向工程:通过无障碍服务收集所有字段,并将它们的 id 和值与用户输入进行比较。
  • 从上一点来看,下一个限制是我们将仅支持浏览器的当前版本。3rd 方浏览器开发人员将来可能会更改 id,我们将不得不更新我们的拦截器以继续支持。这可以通过更新应用程序或通过远程服务器提供浏览器包到 id 映射来完成
  • 我们检测手动用户输入或在链接按下时重定向(因为在这种情况下,新 URL 也将在地址栏中可见)。顺便说一句,不清楚你所说的意思

我只想在地址栏中点击后获取 URL。

  • 最后一个限制。尽管我们可以拦截的URL和重定向用户到另一页,我们不能阻止装载或正在开始加载网站因解析浏览应用程序屏幕的同步延迟。例如,保护用户免受欺诈站点的任何访问并不是真正安全的

实施:

    public class UrlInterceptorService extends AccessibilityService {
        private HashMap<String, Long> previousUrlDetections = new HashMap<>();

        @Override
        protected void onServiceConnected() {
            AccessibilityServiceInfo info = getServiceInfo();
            info.eventTypes = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
            info.packageNames = packageNames();
            info.feedbackType = AccessibilityServiceInfo.FEEDBACK_VISUAL;
            //throttling of accessibility event notification
            info.notificationTimeout = 300;
            //support ids interception
            info.flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS |
                    AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;

            this.setServiceInfo(info);
        }

        private String captureUrl(AccessibilityNodeInfo info, SupportedBrowserConfig config) {
            List<AccessibilityNodeInfo> nodes = info.findAccessibilityNodeInfosByViewId(config.addressBarId);
            if (nodes == null || nodes.size() <= 0) {
                return null;
            }

            AccessibilityNodeInfo addressBarNodeInfo = nodes.get(0);
            String url = null;
            if (addressBarNodeInfo.getText() != null) {
                url = addressBarNodeInfo.getText().toString();
            }
            addressBarNodeInfo.recycle();
            return url;
        }

        @Override
        public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
            AccessibilityNodeInfo parentNodeInfo = event.getSource();
            if (parentNodeInfo == null) {
                return;
            }

            String packageName = event.getPackageName().toString();
            SupportedBrowserConfig browserConfig = null;
            for (SupportedBrowserConfig supportedConfig: getSupportedBrowsers()) {
                if (supportedConfig.packageName.equals(packageName)) {
                    browserConfig = supportedConfig;
                }
            }
            //this is not supported browser, so exit
            if (browserConfig == null) {
                return;
            }

            String capturedUrl = captureUrl(parentNodeInfo, browserConfig);
            parentNodeInfo.recycle();

            //we can't find a url. Browser either was updated or opened page without url text field
            if (capturedUrl == null) {
                return;
            }

            long eventTime = event.getEventTime();
            String detectionId = packageName + ", and url " + capturedUrl;
            //noinspection ConstantConditions
            long lastRecordedTime = previousUrlDetections.containsKey(detectionId) ? previousUrlDetections.get(detectionId) : 0;
            //some kind of redirect throttling
            if (eventTime - lastRecordedTime > 2000) {
                previousUrlDetections.put(detectionId, eventTime);
                analyzeCapturedUrl(capturedUrl, browserConfig.packageName);
            }
        }

        private void analyzeCapturedUrl(@NonNull String capturedUrl, @NonNull String browserPackage) {
            String redirectUrl = "your redirect url is here";
            if (capturedUrl.contains("facebook.com")) {
                performRedirect(redirectUrl, browserPackage);
            }
        }

        /** we just reopen the browser app with our redirect url using service context
         * We may use more complicated solution with invisible activity to send a simple intent to open the url */
        private void performRedirect(@NonNull String redirectUrl, @NonNull String browserPackage) {
            try {
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(redirectUrl));
                intent.setPackage(browserPackage);
                intent.putExtra(Browser.EXTRA_APPLICATION_ID, browserPackage);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                startActivity(intent);
            }
            catch(ActivityNotFoundException e) {
                // the expected browser is not installed
                Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(redirectUrl));
                startActivity(i);
            }
        }

        @Override
        public void onInterrupt() { }

        @NonNull
        private static String[] packageNames() {
            List<String> packageNames = new ArrayList<>();
            for (SupportedBrowserConfig config: getSupportedBrowsers()) {
                packageNames.add(config.packageName);
            }
            return packageNames.toArray(new String[0]);
        }

        private static class SupportedBrowserConfig {
            public String packageName, addressBarId;
            public SupportedBrowserConfig(String packageName, String addressBarId) {
                this.packageName = packageName;
                this.addressBarId = addressBarId;
            }
        }

        /** @return a list of supported browser configs
         * This list could be instead obtained from remote server to support future browser updates without updating an app */
        @NonNull
        private static List<SupportedBrowserConfig> getSupportedBrowsers() {
            List<SupportedBrowserConfig> browsers = new ArrayList<>();
            browsers.add( new SupportedBrowserConfig("com.android.chrome", "com.android.chrome:id/url_bar"));
            browsers.add( new SupportedBrowserConfig("org.mozilla.firefox", "org.mozilla.firefox:id/url_bar_title"));
            return browsers;
        }
    }
Run Code Online (Sandbox Code Playgroud)

和无障碍服务配置:

<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:canRetrieveWindowContent="true"
    android:settingsActivity=".ServiceSettingsActivity" />
Run Code Online (Sandbox Code Playgroud)

随时提出任何问题,我会尽力提供帮助


Jav*_*ban 2

首先,在accessibility_service XML中添加标志和包

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRequestTouchExplorationMode|flagRequestEnhancedWebAccessibility|flagReportViewIds|flagRetrieveInteractiveWindows"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description"
    android:notificationTimeout="0"
    android:canRequestFilterKeyEvents="true"
    android:packageNames="com.android.chrome"
    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity" />
Run Code Online (Sandbox Code Playgroud)

在 AndroidManifest 中:

<service android:name=".MyAccessibilityService"
        android:label="@string/accessibility_title"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService"/>
        </intent-filter>
        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessibility_service" />
    </service>
Run Code Online (Sandbox Code Playgroud)

java类:

public class ASUrl extends AccessibilityService {

      @Override
      public void onAccessibilityEvent(AccessibilityEvent event) {
  if(AccessibilityEvent.eventTypeToString(event.getEventType()).contains("WINDOW")){
         AccessibilityNodeInfo nodeInfo = event.getSource();
         dfs(nodeInfo);
    }
      }

      /**
      * Method to loop through all the views and try to find a URL.
      * @param info
      */
      public void getUrlsFromViews(AccessibilityNodeInfo info) {  
        if(info == null)
          return;

        if(info.getText() != null && info.getText().length() > 0)
          System.out.println(info.getText() + " class: "+info.getClassName());

        for(int i=0;i<info.getChildCount();i++){
         AccessibilityNodeInfo child = info.getChild(i);
         getUrlsFromViews(child);
          if(child != null){
            child.recycle();
          }
        }
      }
 
  }
Run Code Online (Sandbox Code Playgroud)