1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.test.voiceenrollment; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; 22 import android.hardware.soundtrigger.SoundTrigger; 23 import android.hardware.soundtrigger.SoundTrigger.Keyphrase; 24 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 25 import android.os.RemoteException; 26 import android.os.ServiceManager; 27 import android.service.voice.AlwaysOnHotwordDetector; 28 import android.util.Log; 29 30 import com.android.internal.app.IVoiceInteractionManagerService; 31 32 /** 33 * Utility class for the enrollment operations like enroll;re-enroll & un-enroll. 34 */ 35 public class EnrollmentUtil { 36 private static final String TAG = "TestEnrollmentUtil"; 37 38 /** 39 * Activity Action: Show activity for managing the keyphrases for hotword detection. 40 * This needs to be defined by an activity that supports enrolling users for hotword/keyphrase 41 * detection. 42 */ 43 public static final String ACTION_MANAGE_VOICE_KEYPHRASES = 44 KeyphraseEnrollmentInfo.ACTION_MANAGE_VOICE_KEYPHRASES; 45 46 /** 47 * Intent extra: The intent extra for the specific manage action that needs to be performed. 48 * Possible values are {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, 49 * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} 50 * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}. 51 */ 52 public static final String EXTRA_VOICE_KEYPHRASE_ACTION = 53 KeyphraseEnrollmentInfo.EXTRA_VOICE_KEYPHRASE_ACTION; 54 55 /** 56 * Intent extra: The hint text to be shown on the voice keyphrase management UI. 57 */ 58 public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT = 59 KeyphraseEnrollmentInfo.EXTRA_VOICE_KEYPHRASE_HINT_TEXT; 60 /** 61 * Intent extra: The voice locale to use while managing the keyphrase. 62 */ 63 public static final String EXTRA_VOICE_KEYPHRASE_LOCALE = 64 KeyphraseEnrollmentInfo.EXTRA_VOICE_KEYPHRASE_LOCALE; 65 66 /** Simple recognition of the key phrase */ 67 public static final int RECOGNITION_MODE_VOICE_TRIGGER = 68 SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER; 69 /** Trigger only if one user is identified */ 70 public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 71 SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; 72 73 private final IVoiceInteractionManagerService mModelManagementService; 74 75 public EnrollmentUtil() { 76 mModelManagementService = IVoiceInteractionManagerService.Stub.asInterface( 77 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); 78 } 79 80 /** 81 * Adds/Updates a sound model. 82 * The sound model must contain a valid UUID, 83 * exactly 1 keyphrase, 84 * and users for which the keyphrase is valid - typically the current user. 85 * 86 * @param soundModel The sound model to add/update. 87 * @return {@code true} if the call succeeds, {@code false} otherwise. 88 */ 89 public boolean addOrUpdateSoundModel(KeyphraseSoundModel soundModel) { 90 if (!verifyKeyphraseSoundModel(soundModel)) { 91 return false; 92 } 93 94 int status = SoundTrigger.STATUS_ERROR; 95 try { 96 status = mModelManagementService.updateKeyphraseSoundModel(soundModel); 97 } catch (RemoteException e) { 98 Log.e(TAG, "RemoteException in updateKeyphraseSoundModel", e); 99 } 100 return status == SoundTrigger.STATUS_OK; 101 } 102 103 /** 104 * Gets the sound model for the given keyphrase, null if none exists. 105 * This should be used for re-enrollment purposes. 106 * If a sound model for a given keyphrase exists, and it needs to be updated, 107 * it should be obtained using this method, updated and then passed in to 108 * {@link #addOrUpdateSoundModel(KeyphraseSoundModel)} without changing the IDs. 109 * 110 * @param keyphraseId The keyphrase ID to look-up the sound model for. 111 * @param bcp47Locale The locale for with to look up the sound model for. 112 * @return The sound model if one was found, null otherwise. 113 */ 114 @Nullable 115 public KeyphraseSoundModel getSoundModel(int keyphraseId, String bcp47Locale) { 116 if (keyphraseId <= 0) { 117 Log.e(TAG, "Keyphrase must have a valid ID"); 118 return null; 119 } 120 121 KeyphraseSoundModel model = null; 122 try { 123 model = mModelManagementService.getKeyphraseSoundModel(keyphraseId, bcp47Locale); 124 } catch (RemoteException e) { 125 Log.e(TAG, "RemoteException in updateKeyphraseSoundModel"); 126 } 127 128 if (model == null) { 129 Log.w(TAG, "No models present for the gien keyphrase ID"); 130 return null; 131 } else { 132 return model; 133 } 134 } 135 136 /** 137 * Deletes the sound model for the given keyphrase id. 138 * 139 * @param keyphraseId The keyphrase ID to look-up the sound model for. 140 * @return {@code true} if the call succeeds, {@code false} otherwise. 141 */ 142 @Nullable 143 public boolean deleteSoundModel(int keyphraseId, String bcp47Locale) { 144 if (keyphraseId <= 0) { 145 Log.e(TAG, "Keyphrase must have a valid ID"); 146 return false; 147 } 148 149 int status = SoundTrigger.STATUS_ERROR; 150 try { 151 status = mModelManagementService.deleteKeyphraseSoundModel(keyphraseId, bcp47Locale); 152 } catch (RemoteException e) { 153 Log.e(TAG, "RemoteException in updateKeyphraseSoundModel"); 154 } 155 return status == SoundTrigger.STATUS_OK; 156 } 157 158 private boolean verifyKeyphraseSoundModel(KeyphraseSoundModel soundModel) { 159 if (soundModel == null) { 160 Log.e(TAG, "KeyphraseSoundModel must be non-null"); 161 return false; 162 } 163 if (soundModel.uuid == null) { 164 Log.e(TAG, "KeyphraseSoundModel must have a UUID"); 165 return false; 166 } 167 if (soundModel.data == null) { 168 Log.e(TAG, "KeyphraseSoundModel must have data"); 169 return false; 170 } 171 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) { 172 Log.e(TAG, "Keyphrase must be exactly 1"); 173 return false; 174 } 175 Keyphrase keyphrase = soundModel.keyphrases[0]; 176 if (keyphrase.id <= 0) { 177 Log.e(TAG, "Keyphrase must have a valid ID"); 178 return false; 179 } 180 if (keyphrase.recognitionModes < 0) { 181 Log.e(TAG, "Recognition modes must be valid"); 182 return false; 183 } 184 if (keyphrase.locale == null) { 185 Log.e(TAG, "Locale must not be null"); 186 return false; 187 } 188 if (keyphrase.text == null) { 189 Log.e(TAG, "Text must not be null"); 190 return false; 191 } 192 if (keyphrase.users == null || keyphrase.users.length == 0) { 193 Log.e(TAG, "Keyphrase must have valid user(s)"); 194 return false; 195 } 196 return true; 197 } 198 } 199