如何在Android中阅读窗口内容(使用辅助功能服务)并使用绘制其他应用程序权限来唤起UI?

我关于同一主题的最后一个问题不够清楚,被社区搁置,后来被自动删除。因此,我正在详细解释这个问题,以便社区能够以更好的方式理解和帮助。

我想要类似于Voodoo AppMySmartPrice优惠的功能。

现在他们做什么

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

第 2 步:在“辅助功能屏幕”中,它进一步指导如何查找和禁用巫毒教服务。

第 3 步:当我们启用它时,它会进一步要求授予“观察您的操作”和“检索窗口内容”权限。

第 4 步:一旦我们完成了在可访问性屏幕上授予权限,然后移动到一些购物应用程序或通过浏览器访问购物网站并到达产品页面,一个巫毒教浮动按钮会自动弹出。

第 5 步:一旦我们点击它,它就会显示其他应用程序或网站上提供的相同/相关/类似产品的价格,以便用户可以检查可用的最优惠价格。

巫毒教的参考截图

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. 有什么教程可以帮助我吗?虽然我已经在Google上尝试过任何直接教程,但没有取得任何成功。

现在为什么我需要这些信息:

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

现在我从关于这个主题的研究中知道了什么(但我不确定我是否走上了正确的轨道)


答案 1

我试图总结我如何能够在博客文章中解决这个问题。任何需要帮助的人都可以看看它。

将无障碍服务提升到新的水平

我之前的答案被版主删除了,可能是因为我刚刚发布了一个博客链接。所以我再次发布我的答案,并在这里提供详细信息。

最简单的方法是由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 ID 发生更改,则当前逻辑可能会失败。因此,您必须仔细查看构建服务的应用程序。以下是一些示例代码,其中我们获取的数据没有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();
        }
}

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

更新 20/11/2017Google正在暂停所有使用无障碍服务的应用,以用于无障碍以外的其他用途。我收到了一封关于我的一个应用程序的电子邮件,其他开发人员也收到了来自谷歌(Reddit)的相同电子邮件。所以,我认为我们应该寻找一些其他的做事方式,我请求你也在这里更新它,如果有人想出其他实现来实现相同的行为,它也将帮助其他人。

谢谢乌梅什·乔汉


答案 2

我已经成功地制作了这种类型的应用程序,如巫毒教,并成功地获得了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 "";
}

推荐