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 EnrollmentUtil()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 */ addOrUpdateSoundModel(KeyphraseSoundModel soundModel)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 getSoundModel(int keyphraseId, String bcp47Locale)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 */ deleteSoundModel(int keyphraseId, String bcp47Locale)142 public boolean deleteSoundModel(int keyphraseId, String bcp47Locale) { 143 if (keyphraseId <= 0) { 144 Log.e(TAG, "Keyphrase must have a valid ID"); 145 return false; 146 } 147 148 int status = SoundTrigger.STATUS_ERROR; 149 try { 150 status = mModelManagementService.deleteKeyphraseSoundModel(keyphraseId, bcp47Locale); 151 } catch (RemoteException e) { 152 Log.e(TAG, "RemoteException in updateKeyphraseSoundModel"); 153 } 154 return status == SoundTrigger.STATUS_OK; 155 } 156 verifyKeyphraseSoundModel(KeyphraseSoundModel soundModel)157 private boolean verifyKeyphraseSoundModel(KeyphraseSoundModel soundModel) { 158 if (soundModel == null) { 159 Log.e(TAG, "KeyphraseSoundModel must be non-null"); 160 return false; 161 } 162 if (soundModel.getUuid() == null) { 163 Log.e(TAG, "KeyphraseSoundModel must have a UUID"); 164 return false; 165 } 166 if (soundModel.getData() == null) { 167 Log.e(TAG, "KeyphraseSoundModel must have data"); 168 return false; 169 } 170 if (soundModel.getKeyphrases() == null || soundModel.getKeyphrases().length != 1) { 171 Log.e(TAG, "Keyphrase must be exactly 1"); 172 return false; 173 } 174 Keyphrase keyphrase = soundModel.getKeyphrases()[0]; 175 if (keyphrase.getId() <= 0) { 176 Log.e(TAG, "Keyphrase must have a valid ID"); 177 return false; 178 } 179 if (keyphrase.getRecognitionModes() < 0) { 180 Log.e(TAG, "Recognition modes must be valid"); 181 return false; 182 } 183 if (keyphrase.getLocale() == null) { 184 Log.e(TAG, "Locale must not be null"); 185 return false; 186 } 187 if (keyphrase.getText() == null) { 188 Log.e(TAG, "Text must not be null"); 189 return false; 190 } 191 if (keyphrase.getUsers() == null || keyphrase.getUsers().length == 0) { 192 Log.e(TAG, "Keyphrase must have valid user(s)"); 193 return false; 194 } 195 return true; 196 } 197 } 198