Android не может прочитать содержимое окна на нескольких устройствах с помощью службы специальных возможностей

Мое требование: Чтение текста из всплывающего окна, диалогового окна и т. д. для конкретного приложения.

Я внедрил службу специальных возможностей и получаю правильные события и данные в соответствии с моим требованием. Однако во время тестирования я понял, что на некоторых устройствах вместо использования AlertDialog или Dialog они использовали действие (тематическое диалоговое окно). Следовательно, в моем событии доступности я получаю только название действия, есть ли способ найти текст, отображаемый этим конкретным всплывающим действием?

Я довольно усердно искал, но не смог получить много помощи по этой теме, и документация не принесла никакой пользы в этом отношении. В коде службы специальных возможностей не так много, но если вам все еще нужно, я опубликую его позже.

Спасибо


person Ishaan    schedule 21.12.2015    source источник
comment
Я бы воспрепятствовал этому. Хотя есть способы получить доступ ко всей информации. Если вы находитесь в случайных сторонних приложениях, нет надежного способа идентифицировать действие, используемое в качестве AlertDialog, и просто действие. Принуждение к такому поведению только для исправления случайного сценария, в котором разработчики решили делать странные вещи, на самом деле создает проблемы совместимости. По сути, это эквивалент нарушения пользовательского агента в мире веб-доступности.   -  person ChrisCM    schedule 22.12.2015
comment
@ChrisCM: я согласен с тобой. Однако в данном случае это неправда, это не какой-то случайный сценарий. Похоже, это происходит с mediatek sdk, поскольку они использовали AlertActivity вместо AlertDialog по умолчанию для Android. Я разрабатываю приложение с такой функцией, которую я хотел бы поддерживать на всех устройствах, и если я проигнорирую ее, я потеряю потенциальный рынок.   -  person Ishaan    schedule 22.12.2015
comment
В доступности Android иногда поддержка второстепенного сценария означает худшее поведение в ожидаемом сценарии. Это прискорбно, но это правда. Я совершенно уверен, что это один из таких случаев. API специальных возможностей просто не содержат нужной вам информации.   -  person ChrisCM    schedule 23.12.2015
comment
Вы получили какое-либо решение для этого? Я также застрял с этой проблемой.   -  person vishal sharma    schedule 12.04.2016
comment
Нет. Я не нашел решения этой проблемы. Согласно моему исследованию, это диалог внутри действия (UssdAlertActivity), поэтому он возвращает титульный телефон в event.getText(), как упоминал Кунвар Аваниш.   -  person Ishaan    schedule 13.04.2016
comment
я заметил, что эта проблема возникает на устройствах, поддерживающих meditek.   -  person vishal sharma    schedule 14.04.2016
comment
Правильное замечание, я хотел добавить это ранее. Этот UssdAlertActivity принадлежит mediatek sdk, который возвращает телефон вместо текста диалога. Интересно, как в таком случае работают службы доступности, такие как Talkback.   -  person Ishaan    schedule 15.04.2016
comment
Вы также столкнулись с проблемой в executeGlobalAction (GLOBAL_ACTION_BACK), он отлично работал для обычного ответа ussd с сообщением, но ответ с возможностью ввода выбора для пользователя не отвечал на глобальное действие?   -  person vishal sharma    schedule 15.04.2016
comment
Да, GLOBAL_ACTION_BACK работает только в том случае, если диалог можно отменить. Но большинство диалогов оставляют возможность для дальнейшего выбора, как вы сказали, и их нельзя отменить. Так что это больше не работает. Позже я нашел альтернативу отправки широковещательной рассылки с системным диалогом отмены намерения, который также не работает. На данный момент я отказался от этой работы, так как нет решения или обходного пути, который я мог бы найти.   -  person Ishaan    schedule 17.04.2016


Ответы (3)


Используйте accessiblityNodeInfo для получения информации, даже если телефон возвращается, он получит ответ ussd, а также закроет диалоговое окно, когда есть возможность ввести несколько вариантов.

Прежде всего, в случае [pohne] имя класса события возвращается как ussdalertactivty, поэтому я использовал только «предупреждение» для идентификации диалогового окна предупреждения ответа ussd.

public void onAccessibilityEvent(AccessibilityEvent event) {
    if (event.getPackageName().toString().equals("com.android.phone")
                        && event.getClassName().toString().toLowerCase()
                                .contains("alert")) {

                    AccessibilityNodeInfo source = event.getSource();

                    if (source != null) {
                    String pcnResponse = fetchResponse(source);
                    }
    }

Теперь я сделал функцию с именем fetchResponse, которая вернет ответ от pcn в виде строки, а также закроет диалоговое окно, поэтому нужно выполнить PerformGlobalAction (GLOBAL_ACTION_BACK).

private String fetchResponse(AccessibilityNodeInfo accessibilityNodeInfo) {

        String fetchedResponse = "";
        if (accessibilityNodeInfo != null) {
            for (int i = 0; i < accessibilityNodeInfo.getChildCount(); i++) {
                AccessibilityNodeInfo child = accessibilityNodeInfo.getChild(i);
                if (child != null) {

                    CharSequence text = child.getText();

                    if (text != null
                            && child.getClassName().equals(
                                    Button.class.getName())) {

                        // dismiss dialog by performing action click in normal
                        // cases
                        if((text.toString().toLowerCase().equals("ok") || text
                                        .toString().toLowerCase()
                                        .equals("cancel"))) {

                            child.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                            return fetchedResponse;

                        }

                    } else if (text != null
                            && child.getClassName().equals(
                                    TextView.class.getName())) {

                        // response of normal cases
                        if (text.toString().length() > 10) {
                            fetchedResponse = text.toString();
                        }

                    } else if (child.getClassName().equals(
                            ScrollView.class.getName())) {

                        // when response comes as phone then response can be
                        // retrived from subchild
                        for (int j = 0; j < child.getChildCount(); j++) {

                            AccessibilityNodeInfo subChild = child.getChild(j);
                            CharSequence subText = subChild.getText();

                            if (subText != null
                                    && subChild.getClassName().equals(
                                            TextView.class.getName())) {

                                // response of different cases
                                if (subText.toString().length() > 10) {
                                    fetchedResponse = subText.toString();
                                }

                            }

                            else if (subText != null
                                    && subChild.getClassName().equals(
                                            Button.class.getName())) {

                                // dismiss dialog by performing action click in
                                // different
                                // cases
                                if ((subText.toString().toLowerCase()
                                                .equals("ok") || subText
                                                .toString().toLowerCase()
                                                .equals("cancel"))) {
                                    subChild.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                                    return fetchedResponse;
                                }

                            }
                        }
                    }
                }
            }
        }
        return fetchedResponse;
    }

Надеюсь, это решит проблему независимо от устройств.

person vishal sharma    schedule 26.04.2016
comment
Спасибо Вишал. На данный момент у меня нет устройства mediatek, позвольте мне проверить это, как только я получу его в свои руки. - person Ishaan; 29.04.2016
comment
Привет! Что такое переменная isUSSDFromApp? - person Vlad; 12.04.2018
comment
@V.Kalyuzhnyu, извините, issUssdFromApp Я сохранил это для себя. вы можете игнорировать это, и я также отредактировал это в своем ответе. - person vishal sharma; 12.04.2018

Это код, который я использую, и он работает для меня:

public class USSDService extends AccessibilityService {

public static String TAG = USSDService.class.getSimpleName();

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    Log.d(TAG, "onAccessibilityEvent");

    AccessibilityNodeInfo source = event.getSource();
    if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && !String.valueOf(event.getClassName()).contains("AlertDialog")) {
        return;
    }
    if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && (source == null || !source.getClassName().equals("android.widget.TextView"))) {
        return;
    }
    if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && TextUtils.isEmpty(source.getText())) {
        return;
    }

    List<CharSequence> eventText;

    if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
        eventText = event.getText();
    } else {
        eventText = Collections.singletonList(source.getText());
    }

    String text = processUSSDText(eventText);

    if( TextUtils.isEmpty(text) ) return;

    // Close dialog
    performGlobalAction(GLOBAL_ACTION_BACK); // This works on 4.1+ only

    Log.d(TAG, text);
    // Handle USSD response here

}

private String processUSSDText(List<CharSequence> eventText) {
    for (CharSequence s : eventText) {
        String text = String.valueOf(s);
        // Return text if text is the expected ussd response
        if( true ) {
            return text;
        }
    }
    return null;
}

@Override
public void onInterrupt() {
}

@Override
protected void onServiceConnected() {
    super.onServiceConnected();
    Log.d(TAG, "onServiceConnected");
    AccessibilityServiceInfo info = new AccessibilityServiceInfo();
    info.flags = AccessibilityServiceInfo.DEFAULT;
    info.packageNames = new String[]{"com.android.phone"};
    info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
    setServiceInfo(info);
}
}

Объявите это в манифесте Android

<service android:name=".USSDService"
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/ussd_service" />

Create a xml file that describes the accessibility service called ussd_service

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description"
android:notificationTimeout="0"
android:packageNames="com.android.phone" />
person HenBoy331    schedule 21.12.2015
comment
Это все еще не помогает для устройств micromax, lenovo - person Umesh Isran; 22.12.2015
comment
@ HenBoy331: Спасибо за ваш код. Позвольте мне попробовать это. - person Ishaan; 22.12.2015
comment
Я не получаю диалоговое сообщение ussd, используя event.gettext(); я получаю ТЕЛЕФОН .. нужно помочь для оригинального сообщения диалога ussd. - person Kunwar Avanish; 05.04.2016
comment
@KunwarAvanish, что вы получаете в качестве параметра в методе processUSSDText? - person HenBoy331; 05.04.2016
comment
eventText = событие.getText(); ... так же, как и ваш код ... и попробуйте много способов ... помогите, пожалуйста. - person Kunwar Avanish; 05.04.2016
comment
я получаю processUSSDText: только текст [Телефон] .. не получаю диалоговое сообщение с предупреждением. - person Kunwar Avanish; 05.04.2016
comment
Я могу перехватить входящее сообщение ussd, но как сравнить входящее сообщение ussd в случае телефона с двумя SIM-картами? означает, что если я получаю предупреждение о сообщении ussd, то как я могу узнать, что входящее сообщение ussd предназначено для какой симки? - person Sachin Arora; 04.06.2016
comment
Работал нормально для меня. Не забудьте включить специальные возможности для своего приложения в Настройки->Устройство->Специальные возможности->прокрутите вниз, чтобы увидеть свое приложение. - person Abdu; 04.06.2017

вы должны проверить, есть ли какие-либо дочерние узлы для дочерних узлов.

private void clickPerform(AccessibilityNodeInfo nodeInfo)
 {
   if(nodeInfo != null)
 {

            for (int i = 0; i < nodeInfo.getChildCount(); i++) {
                AccessibilityNodeInfo childNode = nodeInfo.getChild(i);
                Log.e("test", "clickPerform: "+childNode );
                if (childNode != null) {
                    for (int j = 0; j <= childNode.getChildCount(); j++) {
                        AccessibilityNodeInfo subChild = childNode.getChild(i);

                        if (String.valueOf(subChild.getText()).toLowerCase().equals("ok")) {
                            subChild.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        } else {
                            Log.e("t2", "clickPerform: ");
                        }
                    }
                }

                }
            }
}
person Bharat Kumar Emani    schedule 17.05.2018