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