Merge "Add tests for model management [SDK Only]" into lmp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ed65c63bdc
@ -1,16 +1,20 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.test.voiceenrollment">
|
||||
|
||||
<uses-permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" />
|
||||
<application
|
||||
android:permission="android.permission.MANAGE_VOICE_KEYPHRASES">
|
||||
<activity android:name="TestEnrollmentActivity" android:label="Voice Enrollment Application"
|
||||
<activity
|
||||
android:name="TestEnrollmentActivity"
|
||||
android:label="Voice Enrollment Application"
|
||||
android:theme="@android:style/Theme.Material.Light.Voice">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.intent.action.MANAGE_VOICE_KEYPHRASES" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data android:name="android.voice_enrollment"
|
||||
<meta-data
|
||||
android:name="android.voice_enrollment"
|
||||
android:resource="@xml/enrollment_application"/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
43
tests/VoiceEnrollment/res/layout/main.xml
Normal file
43
tests/VoiceEnrollment/res/layout/main.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 Google Inc.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enroll"
|
||||
android:onClick="onEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reenroll"
|
||||
android:onClick="onReEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/unenroll"
|
||||
android:onClick="onUnEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
</LinearLayout>
|
22
tests/VoiceEnrollment/res/values/strings.xml
Normal file
22
tests/VoiceEnrollment/res/values/strings.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 Google Inc.
|
||||
|
||||
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.
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<string name="enroll">Enroll</string>
|
||||
<string name="reenroll">Re-enroll</string>
|
||||
<string name="unenroll">Un-enroll</string>
|
||||
</resources>
|
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* 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.android.test.voiceenrollment;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
|
||||
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.service.voice.AlwaysOnHotwordDetector;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.app.IVoiceInteractionManagerService;
|
||||
|
||||
/**
|
||||
* Utility class for the enrollment operations like enroll;re-enroll & un-enroll.
|
||||
*/
|
||||
public class EnrollmentUtil {
|
||||
private static final String TAG = "TestEnrollmentUtil";
|
||||
|
||||
/**
|
||||
* Activity Action: Show activity for managing the keyphrases for hotword detection.
|
||||
* This needs to be defined by an activity that supports enrolling users for hotword/keyphrase
|
||||
* detection.
|
||||
*/
|
||||
public static final String ACTION_MANAGE_VOICE_KEYPHRASES =
|
||||
KeyphraseEnrollmentInfo.ACTION_MANAGE_VOICE_KEYPHRASES;
|
||||
|
||||
/**
|
||||
* Intent extra: The intent extra for the specific manage action that needs to be performed.
|
||||
* Possible values are {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL},
|
||||
* {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL}
|
||||
* or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}.
|
||||
*/
|
||||
public static final String EXTRA_VOICE_KEYPHRASE_ACTION =
|
||||
KeyphraseEnrollmentInfo.EXTRA_VOICE_KEYPHRASE_ACTION;
|
||||
|
||||
/**
|
||||
* Intent extra: The hint text to be shown on the voice keyphrase management UI.
|
||||
*/
|
||||
public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT =
|
||||
KeyphraseEnrollmentInfo.EXTRA_VOICE_KEYPHRASE_HINT_TEXT;
|
||||
/**
|
||||
* Intent extra: The voice locale to use while managing the keyphrase.
|
||||
*/
|
||||
public static final String EXTRA_VOICE_KEYPHRASE_LOCALE =
|
||||
KeyphraseEnrollmentInfo.EXTRA_VOICE_KEYPHRASE_LOCALE;
|
||||
|
||||
/** Simple recognition of the key phrase */
|
||||
public static final int RECOGNITION_MODE_VOICE_TRIGGER =
|
||||
SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
|
||||
/** Trigger only if one user is identified */
|
||||
public static final int RECOGNITION_MODE_USER_IDENTIFICATION =
|
||||
SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
|
||||
|
||||
private final IVoiceInteractionManagerService mModelManagementService;
|
||||
|
||||
public EnrollmentUtil() {
|
||||
mModelManagementService = IVoiceInteractionManagerService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/Updates a sound model.
|
||||
* The sound model must contain a valid UUID,
|
||||
* exactly 1 keyphrase,
|
||||
* and users for which the keyphrase is valid - typically the current user.
|
||||
*
|
||||
* @param soundModel The sound model to add/update.
|
||||
* @return {@code true} if the call succeeds, {@code false} otherwise.
|
||||
*/
|
||||
public boolean addOrUpdateSoundModel(KeyphraseSoundModel soundModel) {
|
||||
if (!verifyKeyphraseSoundModel(soundModel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int status = SoundTrigger.STATUS_ERROR;
|
||||
try {
|
||||
status = mModelManagementService.updateKeyphraseSoundModel(soundModel);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in updateKeyphraseSoundModel", e);
|
||||
}
|
||||
return status == SoundTrigger.STATUS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sound model for the given keyphrase, null if none exists.
|
||||
* This should be used for re-enrollment purposes.
|
||||
* If a sound model for a given keyphrase exists, and it needs to be updated,
|
||||
* it should be obtained using this method, updated and then passed in to
|
||||
* {@link #addOrUpdateSoundModel(KeyphraseSoundModel)} without changing the IDs.
|
||||
*
|
||||
* @param keyphraseId The keyphrase ID to look-up the sound model for.
|
||||
* @param bcp47Locale The locale for with to look up the sound model for.
|
||||
* @return The sound model if one was found, null otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public KeyphraseSoundModel getSoundModel(int keyphraseId, String bcp47Locale) {
|
||||
if (keyphraseId <= 0) {
|
||||
Log.e(TAG, "Keyphrase must have a valid ID");
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyphraseSoundModel model = null;
|
||||
try {
|
||||
model = mModelManagementService.getKeyphraseSoundModel(keyphraseId, bcp47Locale);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in updateKeyphraseSoundModel");
|
||||
}
|
||||
|
||||
if (model == null) {
|
||||
Log.w(TAG, "No models present for the gien keyphrase ID");
|
||||
return null;
|
||||
} else {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the sound model for the given keyphrase id.
|
||||
*
|
||||
* @param keyphraseId The keyphrase ID to look-up the sound model for.
|
||||
* @return {@code true} if the call succeeds, {@code false} otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public boolean deleteSoundModel(int keyphraseId, String bcp47Locale) {
|
||||
if (keyphraseId <= 0) {
|
||||
Log.e(TAG, "Keyphrase must have a valid ID");
|
||||
return false;
|
||||
}
|
||||
|
||||
int status = SoundTrigger.STATUS_ERROR;
|
||||
try {
|
||||
status = mModelManagementService.deleteKeyphraseSoundModel(keyphraseId, bcp47Locale);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in updateKeyphraseSoundModel");
|
||||
}
|
||||
return status == SoundTrigger.STATUS_OK;
|
||||
}
|
||||
|
||||
private boolean verifyKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
|
||||
if (soundModel == null) {
|
||||
Log.e(TAG, "KeyphraseSoundModel must be non-null");
|
||||
return false;
|
||||
}
|
||||
if (soundModel.uuid == null) {
|
||||
Log.e(TAG, "KeyphraseSoundModel must have a UUID");
|
||||
return false;
|
||||
}
|
||||
if (soundModel.data == null) {
|
||||
Log.e(TAG, "KeyphraseSoundModel must have data");
|
||||
return false;
|
||||
}
|
||||
if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
|
||||
Log.e(TAG, "Keyphrase must be exactly 1");
|
||||
return false;
|
||||
}
|
||||
Keyphrase keyphrase = soundModel.keyphrases[0];
|
||||
if (keyphrase.id <= 0) {
|
||||
Log.e(TAG, "Keyphrase must have a valid ID");
|
||||
return false;
|
||||
}
|
||||
if (keyphrase.recognitionModes < 0) {
|
||||
Log.e(TAG, "Recognition modes must be valid");
|
||||
return false;
|
||||
}
|
||||
if (keyphrase.locale == null) {
|
||||
Log.e(TAG, "Locale must not be null");
|
||||
return false;
|
||||
}
|
||||
if (keyphrase.text == null) {
|
||||
Log.e(TAG, "Text must not be null");
|
||||
return false;
|
||||
}
|
||||
if (keyphrase.users == null || keyphrase.users.length == 0) {
|
||||
Log.e(TAG, "Keyphrase must have valid user(s)");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -16,8 +16,106 @@
|
||||
|
||||
package com.android.test.voiceenrollment;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
|
||||
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class TestEnrollmentActivity extends Activity {
|
||||
// TODO(sansid): Add a test enrollment flow here.
|
||||
private static final String TAG = "TestEnrollmentActivity";
|
||||
private static final boolean DBG = true;
|
||||
|
||||
/** Keyphrase related constants, must match those defined in enrollment_application.xml */
|
||||
private static final int KEYPHRASE_ID = 101;
|
||||
private static final int RECOGNITION_MODES = SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
|
||||
private static final String BCP47_LOCALE = "fr-FR";
|
||||
private static final String TEXT = "Hello There";
|
||||
|
||||
private EnrollmentUtil mEnrollmentUtil;
|
||||
private Random mRandom;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (DBG) Log.d(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
mEnrollmentUtil = new EnrollmentUtil();
|
||||
mRandom = new Random();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the enroll button.
|
||||
* Performs a fresh enrollment.
|
||||
*/
|
||||
public void onEnrollButtonClicked(View v) {
|
||||
Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, BCP47_LOCALE, TEXT,
|
||||
new int[] { UserManager.get(this).getUserHandle() /* current user */});
|
||||
UUID modelUuid = UUID.randomUUID();
|
||||
// Generate a fake model to push.
|
||||
byte[] data = new byte[1024];
|
||||
mRandom.nextBytes(data);
|
||||
KeyphraseSoundModel soundModel = new KeyphraseSoundModel(modelUuid, null, data,
|
||||
new Keyphrase[] { kp });
|
||||
boolean status = mEnrollmentUtil.addOrUpdateSoundModel(soundModel);
|
||||
if (status) {
|
||||
Toast.makeText(
|
||||
this, "Successfully enrolled, model UUID=" + modelUuid, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to enroll!!!" + modelUuid, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the un-enroll button.
|
||||
* Clears the enrollment information for the user.
|
||||
*/
|
||||
public void onUnEnrollButtonClicked(View v) {
|
||||
KeyphraseSoundModel soundModel = mEnrollmentUtil.getSoundModel(KEYPHRASE_ID, BCP47_LOCALE);
|
||||
if (soundModel == null) {
|
||||
Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
boolean status = mEnrollmentUtil.deleteSoundModel(KEYPHRASE_ID, BCP47_LOCALE);
|
||||
if (status) {
|
||||
Toast.makeText(this, "Successfully un-enrolled, model UUID=" + soundModel.uuid,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to un-enroll!!!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the re-enroll button.
|
||||
* Uses the previously enrolled sound model and makes changes to it before pushing it back.
|
||||
*/
|
||||
public void onReEnrollButtonClicked(View v) {
|
||||
KeyphraseSoundModel soundModel = mEnrollmentUtil.getSoundModel(KEYPHRASE_ID, BCP47_LOCALE);
|
||||
if (soundModel == null) {
|
||||
Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
// Generate a fake model to push.
|
||||
byte[] data = new byte[2048];
|
||||
mRandom.nextBytes(data);
|
||||
KeyphraseSoundModel updated = new KeyphraseSoundModel(soundModel.uuid,
|
||||
soundModel.vendorUuid, data, soundModel.keyphrases);
|
||||
boolean status = mEnrollmentUtil.addOrUpdateSoundModel(updated);
|
||||
if (status) {
|
||||
Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.uuid,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user