如何阅读窗口内容(使用accessibilityService)并在Android中使用绘制而不是其他应用程序权限来唤起用户界面?

时间:2016-03-07 11:39:38

标签: java android accessibility accessibilityservice android-accessibility

我对同一主题的last问题不够明确,并被社区暂停,之后又被自动删除。所以,我正在详细解释这个问题,以便社区能够以更好的方式理解和帮助。

我希望功能类似于Voodoo AppMySmartPrice优惠。

现在他们做了什么

第1步:当我们第一次打开Voodoo应用程序时,他们会显示一个小教程。在教程结束时,有一个"立即激活"按下按钮,将我们带到辅助功能设置屏幕。

第2步:在“辅助功能”屏幕中,它进一步指导了如何查找和无法使用Voodoo服务。

第3步:当我们启用它时,它会进一步要求授予"观察你的行动"和"检索窗口内容"权限。

第4步:一旦我们完成了对辅助功能屏幕授予权限,并转移到某个购物应用或通过浏览器访问购物网站并到达产品页面,就会自动弹出一个voodoo浮动按钮。

第5步:点击它后,它会显示其他应用或网站上可用的相同/相关/类似产品的价格,以便用户可以查看最佳交易。

参考Voodoo的屏幕截图

Voodoo Screen 1

Accessibility screen

Accessibility screen 2

Permission Granting Screen

Voodoo Screen 2

Voodoo Floating Button on product page

Voodoo Results with the help of draw over other app

现在我想知道形式社区:

  1. 如何在“辅助功能”屏幕和产品详细信息页面上显示我的帮助UI。
  2. 如何检测产品页面何时出现在屏幕上并唤起我的浮动按钮。
  3. 如何获取屏幕上显示的产品名称。
  4. 如何仅为某些应用限制此屏幕阅读功能? (因为我不想在某些版权问题上结束)
  5. 有没有可以帮助我的教程?虽然我已经尝试过谷歌任何直接的教程,但没有取得任何成功。
  6. 现在为什么我需要这些信息:

    我正计划为学生提供一个应用程序,如果它可以在我的服务器上或在可用网站的某个网站上,它们将帮助他们免费获得所需(或类似)书籍(电子书)。我想仅限于某些应用程序的功能,因为某些书籍的版权问题。

    现在我从我的研究中了解到这个主题(但我不确定我是否在正确的轨道上)

2 个答案:

答案 0 :(得分:24)

我曾试图总结一下我是如何通过博客文章解决问题的。任何需要帮助的人都可以查看。

Accessibility Service to the next level

我以前的回答被主持人删除可能是因为我刚刚发布了一个指向博客的链接。所以我也在这里再次发布我的答案。

最简单的方法是由android本身提供。如果您在自己的应用程序上构建一些东西,这是最好和最简单的方法。你必须使用“findAccessibilityNodeInfosByViewId ()“如下所述

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    AccessibilityNodeInfo source = event.getSource(); 
    if (source == null) {
        return; 
    } 
    List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId = source.findAccessibilityNodeInfosByViewId("YOUR PACKAGE NAME:id/RESOURCE ID FROM WHERE YOU WANT DATA"); 
    if (findAccessibilityNodeInfosByViewId.size() > 0) {
        AccessibilityNodeInfo parent = (AccessibilityNodeInfo) findAccessibilityNodeInfosByViewId.get(0);
        // You can also traverse the list if required data is deep in view hierarchy. 
        String requiredText = parent.getText().toString();
        Log.i("Required Text", requiredText);
    }
}  

如果您在其他应用上构建内容并且不知道res id。您必须使用父,子计数,找到数据的级别,事件类型的组合来构建逻辑。您必须在事件或来源中查找模式或上述所述值的组合以获得所需的数据。此外,您必须在代码中设置一些调整,以根据您的任务获得准确的数据。就像在我们的任务中一样,我们有一些正则表达式来获取宝贵的数据。

您必须注意,如果更改了视图或UI或更改了res,则当前逻辑可能会失败。因此,您必须密切关注构建服务的应用程序。以下是一些示例代码,我们在其中获取没有res id的数据,这可能对您了解它将如何工作很有用。

打印源和事件

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    AccessibilityNodeInfo source = event.getSource();
    if (source == null) {
        return;
    } 
    Log.i("Event", event.toString+””); 
    Log.i("Source", source.toString+””);
}

获取子计数

source.getChildCount();

如果子计数> 0,您可能需要查看它。

示例代码1

if (level == 0 && source.getClassName().equals("android.widget.TextView") && source.getText()!=null && !source.getText().toString().isEmpty()){
    // here level is iteration of for loop
    String recivedText = source.getText().toString(); 
    if(source.getClassName().equals("android.widget.TextView") && source.getParent()!=null && source.getParent().getClassName().equals("android.widget.FrameLayout") && source.getParent().getParent()==null){ 
        return recivedText;
    }
}

示例代码2

if (source.getPackageName().equals("PACKAGE NAME OF APP FOR WHICH YOUR EXPECTING EVENT")) {
    if(event.getEventType()==AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && "COMPLETE NAME OF ACTIVITY CLASS WITH PACKAGE NAME (if you want it for some specific screen)".equals(event.getClassName())){
        if(source.getText()!=null && source.getClassName().equals("android.widget.TextView") && source.getParent()!=null && source.getParent().getClassName().equals("android.widget.RelativeLayout")&& source.getParent().getParent()!=null && source.getParent().getParent().getClassName().equals("android.widget.ScrollView") && source.getParent().getParent().getParent()!=null && source.getParent().getParent().getParent().getClassName().equals("android.support.v4.view.ViewPager")&& source.getParent().getParent().getParent().getParent()!=null && source.getParent().getParent().getParent().getParent().getClassName().equals("android.widget.FrameLayout")&& source.getParent().getParent().getParent().getParent().getParent()==null){
        return source.getText().toString();
        }
}

它有点笨拙但是为了让它工作,你必须深入研究日志,找到一个模式,它将带你到你需要的数据。

2017年11月20日更新 谷歌正在暂停使用辅助功能服务的所有应用程序。我收到了一封关于我的应用程序的电子邮件,其他开发者也收到了来自谷歌(Reddit)的同一封电子邮件。所以,我认为我们应该寻找其他一些做事方式,我也请你在这里更新它,如果有人提出其他实现来实现相同的行为,它也会帮助其他人。

由于 Umesh Chauhan

答案 1 :(得分:0)

我已成功完成此类应用程序,如voodoo。并成功获得Flipkart产品页面标题,价格等。 以下方法用于获取用户打开的产品屏幕。

private boolean isOpenFlipkartProductScreen(AccessibilityNodeInfo nodeInfo){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
        if(nodeInfo.getViewIdResourceName()!=null){
            if(nodeInfo.getViewIdResourceName().equals("com.flipkart.android:id/callout_last_linearlayout")){
                Utils.sPrint("You just open product page : " + nodeInfo.getClass());
                isOpenProductPage = true;
                return true;
            }
        }
    }
    return false;
}

并使用以下代码获取产品标题名称和价格。

private String getObtainTitle(AccessibilityNodeInfo sourceInfo){

    if(sourceInfo == null)
        return "";

    AccessibilityNodeInfo nodeInfo = sourceInfo.getParent().getParent();
    if(nodeInfo!=null){
        int i = 0;
        int i2 = 0;
        for(int i3=0;i3<nodeInfo.getChildCount();i3++){

            try{
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    if(nodeInfo.getChild(i3).getViewIdResourceName().equals("com.flipkart.android:id/title_layout")) {
                        i = 0;
                        //   while ( i < nodeInfo.getChild(i).getChildCount()){
                        AccessibilityNodeInfo child = nodeInfo.getChild(i3).getChild(i);
                        String charSequence = child.getText().toString();
                        //   Utils.sPrint("Title is : " + charSequence);
                        if(charSequence!=null){
                            return  charSequence;
                        }
                        // }

                    }
                }
               /*
               *
               * Below code to used for getProduct prise in flipkart app product
               *
               * */
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    if(nodeInfo.getChild(i3).getViewIdResourceName().equals("com.flipkart.android:id/price_layout")){
                        AccessibilityNodeInfo child = nodeInfo.getChild(i3).getChild(i);
                        String charSequence = child.getText().toString();

                        if (child.getText().toString().toUpperCase().startsWith("RS.") ||
                                child.getText().toString().toUpperCase().startsWith("\u20b9")) {
                            String replace = charSequence.replace("Rs.",
                                    "").replace("\u20b9", "");

                            //return replace
                            //   Utils.sPrint("Prise is : " + replace);
                        }

                    }
                }
            }catch (Exception ex){
              //  Utils.sPrint("Ex : " + ex.getMessage());
                return "";
            }

        }
    }
    return "";
}