Intereting Posts
Настройки прокси-сервера Android SDK в LINUX Фрагмент в ViewPager не восстановлен после popBackStack Android-клавиатура webview не появляется в течение длительного времени для ввода значений Подключите Android к сети WiFi Enterprise EAP (PEAP) Gradle (экспериментальный плагин android) игнорирует зависимость проекта Закрыть строку состояния после уведомления Как использовать NodeJS в Android с помощью J2V8 Как мы можем скрыть включение макета программно в Android? Понимание Android «Аппарат радиостанции» для лучшего времени автономной работы Как добавить отслеживание / тайминги экрана в Firebase Analytics? Андроид на прослушивателе изменения текста @ Синглтон в кинжале 2 потокобезопасен? Android ImageView – получить координаты нажатия (щелчок) независимо от местоположения прокрутки или масштаба масштабирования Android peer не аутентифицирован Android. Сделайте вкладки по умолчанию, но без какого-либо контента.

Пользовательские команды для Google Now

Я пытаюсь заставить Google Now принимать пользовательские команды и отправлять намерение в мое приложение, когда выполняется конкретный запрос.

Я сделал это успешно, используя Tasker и Autovoice, но я хочу сделать то же самое без использования этих приложений.

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

Я также попробовал API-интерфейс Voice Interaction, предоставленный Google, что почти то же самое, но это не помогло.

Кто-нибудь здесь достиг этого без использования других приложений, таких как Commander, Autovoice или Tasker?

Solutions Collecting From Web of "Пользовательские команды для Google Now"

В настоящее время Google Now не принимает пользовательские команды. В приложениях, которые вы подробно используете, « перехватить » AccesscessService для перехвата голосовой команды или для укорененных устройств, xposed framework .

Они либо действуют на них, одновременно убивая Google Now, либо игнорируя их, и позволяют Google показывать свои результаты, как обычно.

По многим причинам это плохая идея:

  1. Google найдет способ предотвратить этот тип взаимодействия, если он станет обычным явлением, поскольку они, очевидно, не хотят, чтобы на их обслуживание в настоящее время негативно повлияли.
  2. Он использует жестко закодированные константы, связанные с классами представлений, которые Google использует для отображения голосовой команды. Это, конечно, может быть изменено с каждым выпуском.
  3. Хаки ломаются!

Отказ от ответственности завершен! Используйте на свой риск….

Вам необходимо зарегистрировать AccessibilityService в Manifest :

  <service android:name="com.something.MyAccessibilityService" android:enabled="true" android:label="@string/label" 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/accessibilityconfig" /> </service> 

И добавьте файл конфигурации в res/xml :

 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeWindowContentChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagIncludeNotImportantViews" android:canRetrieveWindowContent="true" android:description="@string/accessibility_description" android:notificationTimeout="100" android:settingsActivity="SettingsActivity"/> 

Вы можете дополнительно добавить:

  android:packageNames="xxxxxx" 

Или расширить функциональность, добавив дополнительные типы событий:

  android:accessibilityEventTypes="typeViewTextSelectionChanged|typeWindowContentChanged|typeNotificationStateChanged" 

Включите следующий класс класса AccessibilityService :

 /* * Copyright (c) 2016 Ben Randall * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.your.package; import android.accessibilityservice.AccessibilityService; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; /** * @author benrandall76 AT gmail DOT com */ public class MyAccessibilityService extends AccessibilityService { private final boolean DEBUG = true; private final String CLS_NAME = MyAccessibilityService.class.getSimpleName(); private static final String GOOGLE_VOICE_SEARCH_PACKAGE_NAME = "com.google.android.googlequicksearchbox"; private static final String GOOGLE_VOICE_SEARCH_INTERIM_FIELD = "com.google.android.apps.gsa.searchplate.widget.StreamingTextView"; private static final String GOOGLE_VOICE_SEARCH_FINAL_FIELD = "com.google.android.apps.gsa.searchplate.SearchPlate"; private static final long COMMAND_UPDATE_DELAY = 1000L; private long previousCommandTime; private String previousCommand = null; private final boolean EXTRA_VERBOSE = false; @Override protected void onServiceConnected() { super.onServiceConnected(); if (DEBUG) { Log.i(CLS_NAME, "onServiceConnected"); } } @Override public void onAccessibilityEvent(final AccessibilityEvent event) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent"); } if (event != null) { switch (event.getEventType()) { case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: checking for google"); } if (event.getPackageName() != null && event.getPackageName().toString().matches( GOOGLE_VOICE_SEARCH_PACKAGE_NAME)) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: checking for google: true"); Log.i(CLS_NAME, "onAccessibilityEvent: event.getPackageName: " + event.getPackageName()); Log.i(CLS_NAME, "onAccessibilityEvent: event.getClassName: " + event.getClassName()); } final AccessibilityNodeInfo source = event.getSource(); if (source != null && source.getClassName() != null) { if (source.getClassName().toString().matches( GOOGLE_VOICE_SEARCH_INTERIM_FIELD)) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: className interim: true"); Log.i(CLS_NAME, "onAccessibilityEvent: source.getClassName: " + source.getClassName()); } if (source.getText() != null) { final String text = source.getText().toString(); if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: interim text: " + text); } if (interimMatch(text)) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: child: interim match: true"); } if (commandDelaySufficient(event.getEventTime())) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandDelaySufficient: true"); } if (!commandPreviousMatches(text)) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandPreviousMatches: false"); } previousCommandTime = event.getEventTime(); previousCommand = text; killGoogle(); if (DEBUG) { Log.e(CLS_NAME, "onAccessibilityEvent: INTERIM PROCESSING: " + text); } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandPreviousMatches: true"); } } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandDelaySufficient: false"); } } break; } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: child: interim match: false"); } } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: interim text: null"); } } } else if (source.getClassName().toString().matches( GOOGLE_VOICE_SEARCH_FINAL_FIELD)) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: className final: true"); Log.i(CLS_NAME, "onAccessibilityEvent: source.getClassName: " + source.getClassName()); } final int childCount = source.getChildCount(); if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: childCount: " + childCount); } if (childCount > 0) { for (int i = 0; i < childCount; i++) { final String text = examineChild(source.getChild(i)); if (text != null) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: child text: " + text); } if (finalMatch(text)) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: child: final match: true"); } if (commandDelaySufficient(event.getEventTime())) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandDelaySufficient: true"); } if (!commandPreviousMatches(text)) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandPreviousMatches: false"); } previousCommandTime = event.getEventTime(); previousCommand = text; killGoogle(); if (DEBUG) { Log.e(CLS_NAME, "onAccessibilityEvent: FINAL PROCESSING: " + text); } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandPreviousMatches: true"); } } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: commandDelaySufficient: false"); } } break; } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: child: final match: false"); } } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: child text: null"); } } } } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: className: unwanted " + source.getClassName()); } if (EXTRA_VERBOSE) { if (source.getText() != null) { final String text = source.getText().toString(); if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: unwanted text: " + text); } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: unwanted text: null"); } } final int childCount = source.getChildCount(); if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: unwanted childCount: " + childCount); } if (childCount > 0) { for (int i = 0; i < childCount; i++) { final String text = examineChild(source.getChild(i)); if (text != null) { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: unwanted child text: " + text); } } } } } } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: source null"); } } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: checking for google: false"); } } break; default: if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: not interested in type"); } break; } } else { if (DEBUG) { Log.i(CLS_NAME, "onAccessibilityEvent: event null"); } } } /** * Check if the previous command was actioned within the {@link #COMMAND_UPDATE_DELAY} * * @param currentTime the time of the current {@link AccessibilityEvent} * @return true if the delay is sufficient to proceed, false otherwise */ private boolean commandDelaySufficient(final long currentTime) { if (DEBUG) { Log.i(CLS_NAME, "commandDelaySufficient"); } final long delay = (currentTime - COMMAND_UPDATE_DELAY); if (DEBUG) { Log.i(CLS_NAME, "commandDelaySufficient: delay: " + delay); Log.i(CLS_NAME, "commandDelaySufficient: previousCommandTime: " + previousCommandTime); } return delay > previousCommandTime; } /** * Check if the previous command/text matches the current text we are considering processing * * @param text the current text * @return true if the text matches the previous text we processed, false otherwise. */ private boolean commandPreviousMatches(@NonNull final String text) { if (DEBUG) { Log.i(CLS_NAME, "commandPreviousMatches"); } return previousCommand != null && previousCommand.matches(text); } /** * Check if the interim text matches a command we want to intercept * * @param text the intercepted text * @return true if the text matches a command false otherwise */ private boolean interimMatch(@NonNull final String text) { if (DEBUG) { Log.i(CLS_NAME, "interimMatch"); } return text.matches("do interim results work"); } /** * Check if the final text matches a command we want to intercept * * @param text the intercepted text * @return true if the text matches a command false otherwise */ private boolean finalMatch(@NonNull final String text) { if (DEBUG) { Log.i(CLS_NAME, "finalMatch"); } return text.matches("do final results work"); } /** * Recursively examine the {@link AccessibilityNodeInfo} object * * @param parent the {@link AccessibilityNodeInfo} parent object * @return the extracted text or null if no text was contained in the child objects */ private String examineChild(@Nullable final AccessibilityNodeInfo parent) { if (DEBUG) { Log.i(CLS_NAME, "examineChild"); } if (parent != null) { for (int i = 0; i < parent.getChildCount(); i++) { final AccessibilityNodeInfo nodeInfo = parent.getChild(i); if (nodeInfo != null) { if (DEBUG) { Log.i(CLS_NAME, "examineChild: nodeInfo: getClassName: " + nodeInfo.getClassName()); } if (nodeInfo.getText() != null) { if (DEBUG) { Log.i(CLS_NAME, "examineChild: have text: returning: " + nodeInfo.getText().toString()); } return nodeInfo.getText().toString(); } else { if (DEBUG) { Log.i(CLS_NAME, "examineChild: text: null: recurse"); } final int childCount = nodeInfo.getChildCount(); if (DEBUG) { Log.i(CLS_NAME, "examineChild: childCount: " + childCount); } if (childCount > 0) { final String text = examineChild(nodeInfo); if (text != null) { if (DEBUG) { Log.i(CLS_NAME, "examineChild: have recursive text: returning: " + text); } return text; } else { if (DEBUG) { Log.i(CLS_NAME, "examineChild: recursive text: null"); } } } } } else { if (DEBUG) { Log.i(CLS_NAME, "examineChild: nodeInfo null"); } } } } else { if (DEBUG) { Log.i(CLS_NAME, "examineChild: parent null"); } } return null; } /** * Kill or reset Google */ private void killGoogle() { if (DEBUG) { Log.i(CLS_NAME, "killGoogle"); } // TODO - Either kill the Google process or send an empty intent to clear current search process } @Override public void onInterrupt() { if (DEBUG) { Log.i(CLS_NAME, "onInterrupt"); } } @Override public void onDestroy() { super.onDestroy(); if (DEBUG) { Log.i(CLS_NAME, "onDestroy"); } } } 

Я сделал класс максимально подробным отступом, поэтому, надеюсь, ему будет легче следовать.

Он делает следующее:

  1. Проверьте, имеет ли тип события правильный тип
  2. Проверьте, установлен ли пакет Google «Сейчас»,
  3. Проверьте информацию о узле для типов жестко закодированных классов
  4. Проверьте временную голосовую команду, когда она загружена в представление
  5. Проверьте окончательную голосовую команду, когда она загружена в представление
  6. Рекурсивно проверить представления для голосовых команд.
  7. Проверьте разницу во времени между событиями
  8. Проверьте, совпадает ли голосовая команда с ранее обнаруженной

Тестировать:

  1. Включить Service в настройках доступности Android
  2. Возможно, вам потребуется перезапустить приложение для правильной регистрации
  3. Запустите распознавание голоса Google и скажите « делать промежуточную работу с результатами »,
  4. Выйти из Google Now
  5. Начните признание и скажите « сделать окончательные результаты »

Вышеприведенное будет демонстрировать извлеченный текст / команду из обоих жестко кодированных представлений. Если вы не перезапустите Google Now, команда будет по-прежнему обнаружена как временная.

Используя извлеченную голосовую команду, вам нужно выполнить собственный язык, чтобы определить, является ли это вашей командой. Если это так, вам нужно запретить Google говорить или показывать результаты. Это достигается путем убийства Google Now или отправки пустой цели голосового поиска, содержащей флаги, которые должны clear/reset task .

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

Надеюсь, это поможет.

РЕДАКТИРОВАТЬ:

Для тех, кто просил «убить» Google Now, вам либо нужно иметь разрешение на уничтожение процессов, либо отправить пустой («») поиск, чтобы очистить текущий поиск:

 public static final String PACKAGE_NAME_GOOGLE_NOW = "com.google.android.googlequicksearchbox"; public static final String ACTIVITY_GOOGLE_NOW_SEARCH = ".SearchActivity"; /** * Launch Google Now with a specific search term to resolve * * @param ctx the application context * @param searchTerm the search term to resolve * @return true if the search term was handled correctly, false otherwise */ public static boolean googleNow(@NonNull final Context ctx, @NonNull final String searchTerm) { if (DEBUG) { Log.i(CLS_NAME, "googleNow"); } final Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); intent.setComponent(new ComponentName(PACKAGE_NAME_GOOGLE_NOW, PACKAGE_NAME_GOOGLE_NOW + ACTIVITY_GOOGLE_NOW_SEARCH)); intent.putExtra(SearchManager.QUERY, searchTerm); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); try { ctx.startActivity(intent); return true; } catch (final ActivityNotFoundException e) { if (DEBUG) { Log.e(CLS_NAME, "googleNow: ActivityNotFoundException"); e.printStackTrace(); } } catch (final Exception e) { if (DEBUG) { Log.e(CLS_NAME, "googleNow: Exception"); e.printStackTrace(); } } return false; } 

Не то, что вы хотите услышать, но текущая версия API не позволяет настраивать голосовые команды:

На странице https://developers.google.com/voice-actions/customaction

Примечание. Мы не принимаем запросы для пользовательских голосовых действий. Следите за обновлениями голосовой почты – разработчиками Google и GoogleDevelopers.