语音输入

借助语音输入功能,您可以创建免触摸操作界面。Glass 为您提供了三种使用语音输入的方法。

主要语音指令从首页卡片启动 Glassware,内容相关语音指令可以在 activity 内执行操作,而系统的语音识别 activity 则允许您从用户处接收自由格式的语音输入。

主要语音指令

这些语音指令会从主屏幕卡片(时钟卡片)中启动 Glassware。声明主语音指令后,如果用户决定通过点按主屏幕卡片启动 Glassware,Glass 会自动创建触摸菜单项作为后备。

要将语音指令添加到 ok glass 语音主菜单中,请执行以下操作:

  1. res/xml/<my_voice_trigger>.xml 中,使用 VoiceTriggers.Command 中定义的某个现有语音指令为语音指令创建一个 XML 资源。例如,此处显示如何使用“开始跑步”。

    <?xml version="1.0" encoding="utf-8"?>
    <trigger command="START_A_RUN" />
    

    如需创建一条语音指令,在启动 activity 或服务之前提示用户说出其他短语,请同时添加一个 input 元素。例如,如果您使用“发布更新”,则可能需要执行此操作。

    <?xml version="1.0" encoding="utf-8"?>
    <trigger command="POST_AN_UPDATE">
        <input prompt="@string/glass_voice_prompt" />
    </trigger>
    
  2. 在 Android 清单中使用 com.google.android.glass.action.VOICE_TRIGGER 操作注册 intent 过滤器。如果 intent 过滤器检测到用户说出您的语音指令,则会启动相应 activity 或服务。

    <?xml version="1.0" encoding="utf-8"?>
    <application ...>
        <activity | service ...>
            <intent-filter>
                <action android:name=
                        "com.google.android.glass.action.VOICE_TRIGGER" />
            </intent-filter>
            <meta-data android:name="com.google.android.glass.VoiceTrigger"
                android:resource="@xml/my_voice_trigger" />
        </activity | service>
        // ...
    </application>
    
  3. 为您的 activity 或服务声明 android:icon 属性。 这样,Glass 可以在正常,玻璃触摸菜单中显示 Glassware 图标。

    <activity |service
        android:icon="@drawable/my_icon" ...>
      ...
    </activity | service>
    
  4. 如果您的语音指令使用语音提示并启动 activity,请通过以下代码(例如 onResume())获取所有转录的文本:

    ArrayList<String> voiceResults = getIntent().getExtras()
            .getStringArrayList(RecognizerIntent.EXTRA_RESULTS);
    

    如果语音指令启动服务,intent extra 将在 onStartCommand() 回调中提供:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        ArrayList<String> voiceResults = intent.getExtras()
                .getStringArrayList(RecognizerIntent.EXTRA_RESULTS);
        // ...
    }
    

设置限制

如果您需要以下一项或多项功能来启动 Glassware,请在 res/xml/<my_voice_trigger>.xml 资源中指定这些功能。如果这些功能不可用,Google Glass 会停用语音指令:

  • camera
  • network
  • microphone

    <trigger command="POST_AN_UPDATE">
        <constraints
            camera="true"
            network="true" />
    </trigger>
    

上下文相关语音指令

上下文语音指令允许用户从 activity 执行操作。 您可以使用标准 Android 菜单 API 构建上下文语音指令,但用户可以使用语音指令(而不是轻触方式)调用菜单项。

如需为特定 activity 启用上下文语音指令,请执行以下操作:

  1. 在所需 activity 中调用 getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS),以启用上下文语音指令。启用此功能后,每当此 activity 获得焦点时,屏幕的页脚区域都会显示“ok glass”菜单。

  2. 替换 onCreatePanelMenu() 并处理启用 WindowUtils.FEATURE_VOICE_COMMANDS 的情况。启用此选项后,您可以在此处执行一次性菜单设置,例如膨胀菜单资源或调用 Menu.add() 方法来创建语音菜单系统。

  3. 替换 onMenuItemSelected() 以在用户说出语音指令时对其进行处理。当用户选择完菜单项后,只要 activity 保持在焦点位置,“ok, glass”语音指令会自动重新出现在屏幕的页脚部分,准备接受新的语音指令。

    以下代码可启用上下文语音指令,适时扩充菜单资源,并在用户说出语音指令时对其进行处理:

    public class ContextualMenuActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle bundle) {
            super.onCreate(bundle);
    
            // Requests a voice menu on this activity. As for any other
            // window feature, be sure to request this before
            // setContentView() is called
            getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS);
            setContentView(R.layout.activity_main);
        }
    
        @Override
        public boolean onCreatePanelMenu(int featureId, Menu menu) {
            if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) {
                getMenuInflater().inflate(R.menu.main, menu);
                return true;
            }
            // Pass through to super to setup touch menu.
            return super.onCreatePanelMenu(featureId, menu);
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
    
        @Override
        public boolean onMenuItemSelected(int featureId, MenuItem item) {
            if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) {
                switch (item.getItemId()) {
                    case R.id.dogs_menu_item:
                        // handle top-level dogs menu item
                        break;
                    case R.id.cats_menu_item:
                        // handle top-level cats menu item
                        break;
                    case R.id.lab_menu_item:
                        // handle second-level labrador menu item
                        break;
                    case R.id.golden_menu_item:
                        // handle second-level golden menu item
                        break;
                    case R.id.calico_menu_item:
                        // handle second-level calico menu item
                        break;
                    case R.id.cheshire_menu_item:
                        // handle second-level cheshire menu item
                        break;
                    default:
                        return true;
                }
                return true;
            }
            // Good practice to pass through to super if not handled
            return super.onMenuItemSelected(featureId, item);
        }
    }
    

    下面是一个上一个 activity 使用的菜单资源的示例。请注意,如何为分层语音菜单系统创建嵌套菜单项。在下面的示例中,第一个菜单项可通过 ok glass, Show me dogs, Labrador 访问。

    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- Use the constants defined in the ContextualMenus.Command enum-->
        <item
            android:id="@+id/dogs_menu_item"
            android:title="@string/show_me_dogs">
            <menu>
                <item
                    android:id="@+id/lab_menu_item"
                    android:title="@string/labrador" />
                <item
                    android:id="@+id/golden_menu_item"
                    android:title="@string/golden" />
            </menu>
        </item>
        <item
            android:id="@+id/cats_menu_item"
            android:title="@string/show_me_cats">
            <menu>
                <item
                    android:id="@+id/cheshire_menu_item"
                    android:title="@string/cheshire" />
                <item
                    android:id="@+id/calico_menu_item"
                    android:title="@string/calico" />
            </menu>
        </item>
    </menu>
    
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <!-- Use the constants defined in the ContextualMenus.Command enum-->
        <item
            android:id="@+id/play_menu_item"
            android:title="PLAY_MUSIC" />
        <item
            android:id="@+id/pause_menu_item"
            android:title="PAUSE_MUSIC" />
    </menu>
    
  4. (可选)替换 onPreparePanel(),检查 WindowUtils.FEATURE_VOICE_COMMANDS 是否处于启用状态。启用此选项后,您可以执行其他逻辑来设置菜单系统,例如根据某些条件添加和移除特定菜单项。此外,您还可以根据某些条件开启(返回 true)和关闭(返回 false)上下文菜单语音菜单。例如:

        private boolean mVoiceMenuEnabled;
        ...
        @Override
        public boolean onPreparePanel(int featureId, View view, Menu menu) {
            if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) {
            // toggle this boolean on and off based on some criteria
                return mVoiceMenuEnabled;
            }
            // Good practice to call through to super for other cases
            return super.onPreparePanel(featureId, view, menu);
        }
    

同时支持语音菜单和触摸菜单

由于上下文语音指令使用现有的 Android 菜单 API,因此您可以重复使用已有的针对触摸菜单的大量代码和资源,同时支持这两种菜单。

您现在只需要执行 Window.FEATURE_OPTIONS_PANEL 功能,另外,您已经通过多种方法检查了 WindowUtils.FEATURE_VOICE_COMMANDS 功能,然后添加逻辑以打开用户操作(例如点按)中的触摸菜单。

例如,您可以更改上一个 activity 示例,以添加对触摸菜单的支持(如下所示):

// 1. Check for Window.FEATURE_OPTIONS_PANEL
// to inflate the same menu resource for touch menus.
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
    if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS ||
            featureId == Window.FEATURE_OPTIONS_PANEL) {
    ...
}

// 2. Check for Window.FEATURE_OPTIONS_PANEL
// to handle touch menu item selections.
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
    if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS ||
            featureId == Window.FEATURE_OPTIONS_PANEL) {
    ...
}

进行这些更改后,您可以点按或说出 ok glass 以显示您的菜单。

使用未列出的语音指令进行开发

如果您想分发 Glassware,必须使用 VoiceTriggers.Command 中批准的主语音指令以及 ContextualMenus.Command 中批准的上下文语音指令。

如果您想使用 GDK 中未提供的语音指令,可以在 AndroidManifest.xml 文件中请求 Android 权限:

<uses-permission
     android:name="com.google.android.glass.permission.DEVELOPMENT" />

使用未列出的主语音指令

  1. res/values/strings.xml 中声明一个字符串,用于定义语音触发器的名称。(可选)声明语音提示,以在启动 Glassware 之前显示语音识别软件。

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="glass_voice_trigger">read me a story</string>
        <string name="glass_voice_prompt">what story?</string>
    </resources>
    
  2. res/xml/<my_voice_trigger>.xml 中为语音触发器创建 XML 资源。对于不公开的语音指令,您应该使用 keyword 属性,而不是用于已批准的语音指令的 command 属性。keyword 属性应该是对定义语音指令的字符串资源的引用。对于会立即启动 activity 或服务的简单语音触发器,只需指定 trigger 元素即可:

    <?xml version="1.0" encoding="utf-8"?>
    <trigger keyword="@string/glass_voice_trigger" />
    

    如需创建语音触发器来提示用户在启动 activity 或服务之前说出额外短语,请同时包含一个输入元素:

    <?xml version="1.0" encoding="utf-8"?>
    <trigger keyword="@string/glass_voice_trigger">
        <input prompt="@string/glass_voice_prompt" />
    </trigger>
    

使用不公开的上下文语音指令

创建菜单项时,请使用任意文字作为菜单项的标题。 例如:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Use the constants defined in the ContextualMenus.Command enum-->
    <item
        android:id="@+id/pizza_menu_item"
        android:title="@string/find_pizza" />
</menu>

启动语音识别


语音识别 Glassware 等待用户说话,并在完成后返回转录的文本。如需启动 activity,请执行以下操作:

  1. 使用 ACTION_RECOGNIZE_SPEECH intent 调用 startActivityForResult()。启动 activity 时,支持以下 intent extra:
  2. 替换 onActivityResult() 回调以从 EXTRA_RESULTS intent extra 接收转录的文本。当用户完成发言时,系统会调用此回调。

    private static final int SPEECH_REQUEST = 0;
    
    private void displaySpeechRecognizer() {
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        startActivityForResult(intent, SPEECH_REQUEST);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        if (requestCode == SPEECH_REQUEST && resultCode == RESULT_OK) {
            List<String> results = data.getStringArrayListExtra(
                    RecognizerIntent.EXTRA_RESULTS);
            String spokenText = results.get(0);
            // Do something with spokenText.
        }
        super.onActivityResult(requestCode, resultCode, data);
    }