Форум программистов, компьютерный форум, киберфорум
Наши страницы
Программирование Android
Войти
Регистрация
Восстановить пароль
 
InessaSuper
97 / 4 / 0
Регистрация: 09.05.2015
Сообщений: 70
1

Артефакт при осуществлении первой попытки обратного вызова для метода onAccessibilityStateChanged

20.03.2017, 13:27. Просмотров 392. Ответов 12

Я использую в своем приложении функциональность, связанную с дополнительными возможностями, представляемыми через использование служб Accessibility Services для операционной системы Андроид.

Созданная для моего приложения служба Accessibility Service первоначально находится в отключенном состоянии.

При запуске своего приложения я проверяю включена ли для моего приложения моя служба Accessibility Service с заданным именем.

Если соответствующая служба не включена, я вызываю Активити с системными настройками Андроида, где пользователь может вручную включить службу Accessibility Service для моего приложения.

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

Т.е. сразу же после включения в Настройках моей службы Accessibility Service, необходимо осуществить проверку того, что эта служба оказалась включенной.

Для этого я использую подход, связанный с использованием обратного вызова метода, который отрабатывает AccessibilityStateChangeListener для созданного мной экземпляра AccessibilityManager.

Ниже будет приведен фрагмент кода из файла MainActivity.java моего приложения, который отвечает за регистрацию обратного вызова метода onAccessibilityStateChanged, проверяющего состояние того, включена ли моя служба Accessibility Service.

По какой-то непонятной причине при первом вызове этого метода моя служба не появляется в списке включенных служб, получаемых при помощи вызова метода getEnabledAccessibilityServiceList().

При последующем нажатии кнопки "Выкл.", а затем "Вкл." для службы Accessibility Service моего приложения вызов метода onAccessibilityStateChanged() начинает корректно отображать состояние включенности моей службы.

1. Скажите, по какой причине метод getEnabledAccessibilityServiceList() при первоначальном отработке события обратного вызова onAccessibilityStateChanged() возвращает некорректные сведения о состоянии включенности моей службы?

Таким образом, чтобы обойти этот артефакт я предполагаю осуществить вызов события обратного вызова метода onAccessibilityStateChanged() ВРУЧНУЮ.

Только я не понимаю, как это можно сделать.

2. Подскажите, пожалуйста, каким образом можно ВРУЧНУЮ (т.е. программным образом) осуществить вызов метода onAccessibilityStateChanged() в самом конце метода onCreate() класса MainActivity моего приложения?

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.comp.serviceaccessibility;
 
import java.util.List;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
 
public class MainActivity extends Activity {
 
      final static String LOG_TAG = "myLogs";
      
      public static String AccessibilityServiceId = "com.comp.serviceaccessibility/.AppAccessibilityService";     
      public static Boolean AcessibilityServiceIsEnabled;     
      public static AccessibilityManager accessibilityManager;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        // Add Accessibility Service change listener        
        accessibilityManager = (AccessibilityManager)getSystemService(Context.ACCESSIBILITY_SERVICE);                
        accessibilityManager.addAccessibilityStateChangeListener(new AccessibilityManager.AccessibilityStateChangeListener() 
        {
            @Override
            public void onAccessibilityStateChanged(boolean b) 
            {
                Log.d(LOG_TAG, "onAccessibilityStateChanged");
                AcessibilityServiceIsEnabled = false;
                
                List<AccessibilityServiceInfo> runningServices = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
                                
                for (AccessibilityServiceInfo service : runningServices) 
                {
                    if (AccessibilityServiceId.equals(service.getId())) 
                    {
                        AcessibilityServiceIsEnabled = true;
                    }
                }
                
                Toast.makeText(MainActivity.this, "AcessibilityServiceIsEnabled: "+ AcessibilityServiceIsEnabled.toString(), Toast.LENGTH_SHORT).show();
            }
        });            
        
        // ??? What code should I insert here to call onAccessibilityStateChanged() method MANUALLY?
    }
        
    public void onStartAccessibilitySettingsActivity(View v) 
    {
        Log.d(LOG_TAG, "Turn On Service In Settings");
        
        // Start Accessibility Settings Activity
        Intent intentAccessibilitySettings = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);      
        PackageManager packageManager = getPackageManager(); 
        ComponentName componentName = intentAccessibilitySettings.resolveActivity(packageManager);
        if (componentName != null) {
            try {
                startActivity(intentAccessibilitySettings);    
            } catch (ActivityNotFoundException ex) {
                Log.d(LOG_TAG, "Accessibility Settings Activity is not found");       
            }
        } 
      }    
}
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.03.2017, 13:27
Ответы с готовыми решениями:

Метод фрагмента onResume() перестает вызываться после вызова метода recreate() для Activity
Такая проблема:после вызова метода recreate() методы фрагментов onCreate() and...

Вызова метода по таймеру
Есть рабочий метод, реализовывающий изменение TextView по таймеру. Скопипастил...

Неправильная периодичность вызова метода по таймеру
Есть необходимость запускать некую задачу в фоне по таймеру. Для этого я...

Не удалось найти значение для обратного вызова или не был реализован ICallbakEventHandler
Здравствуйте форумчане. Нужна ваша помощь. Работаю с компонентами DevExpress и...

При попытке вызова метода ничего не происходит. Код не отрабатывает.
Здравствуйте Решил разобраться с классами На нескольких сайтах нашёл пример...

12
vxg
Модератор
3252 / 2052 / 323
Регистрация: 13.01.2012
Сообщений: 7,950
20.03.2017, 13:35 2
InessaSuper, при включении службы вызывается какой-либо метод самой службы (риторический вопрос) - почему бы просто не ловить ее состояние в этом методе вместо того что бы цеплять слушатели?
0
Pablito
2731 / 2166 / 735
Регистрация: 12.05.2014
Сообщений: 7,583
Завершенные тесты: 1
20.03.2017, 14:19 3
Цитата Сообщение от InessaSuper Посмотреть сообщение
После того, как пользователь нажмёт на кнопку включения моей службы в Настройках, мне необходимо отследить событие соответствующее тому, что произошло изменение состояния моей службы.
в сервисе вызывается onServiceConnected

Добавлено через 31 минуту
а вообще если делать через слушатель, как в коде выше то там прилетает переменная onAccessibilityStateChanged(boolean b)
которая показывает включили сервис или нет

и я не понимаю зачем там этот дикий цикл с перебором
1
InessaSuper
97 / 4 / 0
Регистрация: 09.05.2015
Сообщений: 70
20.03.2017, 16:49  [ТС] 4
Первоначально я тоже предполагала использовать обработчик событий onServiceConnected в запущенной мною службе.

При этом в обработчике события onServiceConnected в классе своей службы AppAccessibilityService я планировала изменять значение соответствующего флага isEnabled в true, индицирующего таким образом, что служба запущена.

Но я столкнулась с тем, что в документации по API для Accessibility Services не определен обработчик события onServiceDisconnected как для обычных служб.

Таким образом, у меня нет возможности изменить значение флага isEnabled в false для созданной моим приложением службы Accessibility Service в том случае, когда пользователь отключает разрешение на её использование в Настройках.

Я пыталась использовать унаследованный от обычной службы обработчик события onDestroy(), но для служб Accessibility Services это событие отрабатывает, когда служба удаляется вместе с деинсталляцией использующего её приложения.

А когда же я просто в Настройках отключаю свою службу Accessibility Service, то управление не передаётся обработчику события onDestroy().

И нет никакого другого обработчика события отключения службы Accessibility Service, в котором я могла бы перевести значение соответствующего флага isEnabled в fasle.

Поэтому мне всё равно необходимо было бы использовать слушатель AccessibilityStateChangeListener для того, чтобы проверять значение флага isEnabled в ссылке на экземпляр класса своей службы AppAccessibilityService.

Таким образом, мне в любом случае необходимо использовать обработчик onAccessibilityStateChanged() для слушателя AccessibilityStateChangeListener, т.к. я не могу передавать поток управления ходом выполнения операций из своего приложения куда-либо (в том числе и в запущенной мною службу, которая "живёт своей жизнью").

Каким образом я осуществляю проверку "запущенности" моей службы Accessibility Service в своём приложении - это вопрос вторичный.

Цикл с перебором для всех запущенных служб с помощью вызова метода getEnabledAccessibilityServiceList() в обработчике события onAccessibilityStateChanged используется мной потому, что обработчик onAccessibilityStateChanged отрабатывает при изменении ЛЮБОЙ из установленных на мобильном устройстве служб Accessibility Service, но не конкретизирует какая именно служба изменила своё состояние.

Главный мой вопрос заключается в том, каким образом я могу вызвать обработчик onAccessibilityStateChanged() вручную программным образом, чтобы исключить влияние описанного мною выше артефакта?
0
Pablito
2731 / 2166 / 735
Регистрация: 12.05.2014
Сообщений: 7,583
Завершенные тесты: 1
20.03.2017, 16:55 5
Цитата Сообщение от InessaSuper Посмотреть сообщение
у меня нет возможности изменить значение флага isEnabled в false для созданной моим приложением службы Accessibility Service в том случае, когда пользователь отключает разрешение на её использование в Настройках.
я когда проверял, то onUnbind() в сервисе срабатывал каждый раз когда с выключал службу

Добавлено через 1 минуту
Цитата Сообщение от InessaSuper Посмотреть сообщение
но не конкретизирует какая именно служба изменила своё состояние.
поэтому я и предложил следить за onServiceConnected() в сервисе
вместо стрёмных статических флагов оттуда лучше кидать интент, который будет ловить активити или сервис - смотря как надо реагировать
0
InessaSuper
97 / 4 / 0
Регистрация: 09.05.2015
Сообщений: 70
20.03.2017, 17:58  [ТС] 6
Мне кажется я нашла причину возникновения описанного мною артефакта, но не могу понять как мне с ним бороться.

Я обратила внимание, что в протоколе LogCat при первоначальном включении моей службы происходит сначала запуск обработчика метода onAccessibilityStateChanged() для слушателя AccessibilityStateChangeListener из класса MainActivity, а затем уж отрабатывает обработчик метода onServiceConnected() из класса моей службы AppAccessibilityService.

Поэтому какой бы подход я не использовала для определения того, запущена ли моя служба: во время её первого запуска, по какой-то причине, всегда отрабатывает "НЕПРАВИЛЬНАЯ" последовательность событий:

Последовательность 1

- MainActivity.accessibilityManager.AccessibilityStateChangeListener.onAccessibilityStateChanged()
- AppAccessibilityService.onServiceConnected()

А вот уже затем после выключения и последующего включения моей службы всё отрабатывает ПРАВИЛЬНО и как надо в следующей последовательности:

Последовательность 2

- AppAccessibilityService.onServiceConnected()
- MainActivity.accessibilityManager.AccessibilityStateChangeListener.onAccessibilityStateChanged()

Вопрос заключается в следующем.

Какова причина возникновения описанного мною артефакта и как можно сделать так, чтобы запуск необходимых мне обработчиков запускался сразу в соответствии со 2-й последовательностью?
0
Pablito
2731 / 2166 / 735
Регистрация: 12.05.2014
Сообщений: 7,583
Завершенные тесты: 1
20.03.2017, 18:24 7
Цитата Сообщение от InessaSuper Посмотреть сообщение
- MainActivity.accessibilityManager.AccessibilityStateChangeListener.onAccessibilityStateChanged()
убрать вообще

зачем вообще в MainActivity вешается этот слушатель?
есть сервис - пусть он кидает интенты при старте/останове и на них уже реагирует активити
0
InessaSuper
97 / 4 / 0
Регистрация: 09.05.2015
Сообщений: 70
20.03.2017, 18:59  [ТС] 8
Я завела этот слушатель в MainActivity для того, чтобы в потоке выполнения приложения сделать как бы "остановку", пока пользователь не включит мою службу AppAccessibilityService.

Сразу же после того, как пользователь "включает" мою службу, я в обработчике слушателя передаю выполнение исполняемого потока своего приложения далее.

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

Подскажите, пожалуйста, что имется ввиду под высказыванием "есть сервис - пусть он кидает интенты при старте/останове и на них уже реагирует активити"?

Я ещё не волшебница, а только учусь...

Ниже приведен мой класс AppAccessibilityService, который отслеживает activityName в запускаемых приложениях на Андроид устройстве.

Скажите, каким образом необходимо изменить классы AppAccessibilityService и MainActivity, чтобы реализовать рекомендуемый подход с "киданием интентов при старте/останове и реагировании на них активити"?

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.comp.serviceaccessibility;
 
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
 
public class AppAccessibilityService extends AccessibilityService {
 
    public static String activityName;
 
    @Override
    public void onServiceConnected() {      
        Log.i("***", "onServiceConnected ");
        AccessibilityServiceInfo info = new AccessibilityServiceInfo();
        info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        info.notificationTimeout = 100;
        info.feedbackType = AccessibilityEvent.TYPES_ALL_MASK;
        setServiceInfo(info);
    }
        
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.i("***", "onAccessibilityEvent ");
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 
            activityName = String.valueOf(event.getClassName());
            Log.i("***", "onAccessibilityEvent " + activityName);
            //Toast.makeText(getApplicationContext(), activityName, Toast.LENGTH_LONG).show();            
        }
    }
 
    @Override
    public void onInterrupt() {
        Log.i("***", "onInterrupt ");
    }
}
0
Pablito
2731 / 2166 / 735
Регистрация: 12.05.2014
Сообщений: 7,583
Завершенные тесты: 1
20.03.2017, 19:59 9
ну вот например есть сервис, я оттуда все лишнее вытер, оставил строки, которые надо перенести в свой сервис
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyAccessibilityService extends AccessibilityService {
 
    static final String ACTION_SERVICE_STARTED = "ACTION_SERVICE_STARTED";
    static final String ACTION_SERVICE_STOPPED = "ACTION_SERVICE_STOPPED";
 
    @Override
    public boolean onUnbind(Intent intent) {
        sendMessageToActivity(false);
        return super.onUnbind(intent);
    }
 
    @Override
    protected void onServiceConnected() {
        sendMessageToActivity(true);
    }
 
    private void sendMessageToActivity(boolean isStarted) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.setAction(isStarted ? ACTION_SERVICE_STARTED : ACTION_SERVICE_STOPPED);
        startActivity(intent);
    }
}
и минимальный код в активити
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        Intent intent = new Intent(MainActivity.this, MyAccessibilityService.class);
        startService(intent);
 
        if (getIntent() != null) {
            doSomething();
        }
    }
 
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
    }
 
    private void doSomething() {
        String action = getIntent().getAction();
        if (MyAccessibilityService.ACTION_SERVICE_STARTED.equals(action)) {
            Log.i("***", "onNewIntent: SERVICE STARTED");
        } else if (MyAccessibilityService.ACTION_SERVICE_STOPPED.equals(action)) {
            Log.i("***", "onNewIntent: SERVICE STOPPED");
        }
    }
}
1
InessaSuper
97 / 4 / 0
Регистрация: 09.05.2015
Сообщений: 70
21.03.2017, 10:53  [ТС] 10
Приведенный выше код отлично работает.

Для того, чтобы его запустить мне понадобилось добавить ещё одну строчку кода в метод sendMessageToActivity() класса MyAccessibilityService (intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), чтобы избежать следующей ошибки возникшей при компиляции:

android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

Теперь метод sendMessageToActivity() выглядит следующим образом:

Java
1
2
3
4
5
6
7
    private void sendMessageToActivity(boolean isStarted) 
    {
        Intent intent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(isStarted ? ACTION_SERVICE_STARTED : ACTION_SERVICE_STOPPED);
        startActivity(intent);
    }
В процессе поиска этого решения на форуме stackoverflow я обнаружила следующий комментарий к найденному решению:

Adding Intent.FLAG_ACTIVITY_NEW_TASK will solve your error, but make sure if you need this flag or not as it will trigger the activity as new task which you may not want in your scenario.

Start new Activity outside the Activity context (см. 3-й ответ)
http://stackoverflow.com/questions/1...tivity-context


Я хочу понять, что критичного в том, что в моём случае MainActivity будет запускаться как новый таск?

Какие проблемы может за собой повлечь использование этого подхода в дальнейшей работе моего приложения?
0
vxg
Модератор
3252 / 2052 / 323
Регистрация: 13.01.2012
Сообщений: 7,950
22.03.2017, 08:53 11
InessaSuper, новый таск это к примеру у вас уже запущена активити со своим стеком возвратов и тут бац ещё одна такая же на пустом месте
0
Pablito
22.03.2017, 10:39
  #12

Не по теме:

да там на самом деле проще просто прочитать внимательно что написано в подсказке по Ctrl+Q для каждого из вариантов (и на флагах и в манифесте), а не ждать пока на форуме кто-то потрудится настрочить пол страницы текста

0
vxg
22.03.2017, 11:00     Артефакт при осуществлении первой попытки обратного вызова для метода onAccessibilityStateChanged
  #13

Не по теме:

Цитата Сообщение от Паблито Посмотреть сообщение
да там на самом деле проще просто прочитать внимательно
прочитать /попробовать /офигеть /напиться /еще раз прочитать /еще раз попробовать /еще раз офигеть и в конце концов просто опытным путем выяснить нечто необъяснимое в комбинации этих казалось бы понятных флажков удовлетворяющее вашим требованиям)

0
22.03.2017, 11:00
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
22.03.2017, 11:00
Привет! Вот еще темы с ответами:

Выбор метода для вызова с varags параметрами
Какой результат попытки компиляции и запуска программы: public class...

Ошибка вызова метода для блока с параметром
source_file.rb:18:in `&lt;main&gt;': undefined method `power' for main:Object...

Передача всего объекта в метод Main для вызова метода
Добрый день! Подскажите пожалуйста. Есть например класс Student. В нём есть...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru