KeyphraseEnrollmentInfo.java revision e6cd2476aa9d07df0de0a0081ab66d8401a7e228
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 17package android.hardware.soundtrigger; 18 19import android.Manifest; 20import android.content.Intent; 21import android.content.pm.ApplicationInfo; 22import android.content.pm.PackageManager; 23import android.content.pm.ResolveInfo; 24import android.content.res.Resources; 25import android.content.res.TypedArray; 26import android.content.res.XmlResourceParser; 27import android.service.voice.AlwaysOnHotwordDetector; 28import android.util.AttributeSet; 29import android.util.Slog; 30import android.util.Xml; 31 32import org.xmlpull.v1.XmlPullParser; 33import org.xmlpull.v1.XmlPullParserException; 34 35import java.io.IOException; 36import java.util.List; 37 38/** 39 * Enrollment information about the different available keyphrases. 40 * 41 * @hide 42 */ 43public class KeyphraseEnrollmentInfo { 44 private static final String TAG = "KeyphraseEnrollmentInfo"; 45 /** 46 * Name under which a Hotword enrollment component publishes information about itself. 47 * This meta-data should reference an XML resource containing a 48 * <code><{@link 49 * android.R.styleable#VoiceEnrollmentApplication 50 * voice-enrollment-application}></code> tag. 51 */ 52 private static final String VOICE_KEYPHRASE_META_DATA = "android.voice_enrollment"; 53 /** 54 * Activity Action: Show activity for managing the keyphrases for hotword detection. 55 * This needs to be defined by an activity that supports enrolling users for hotword/keyphrase 56 * detection. 57 */ 58 public static final String ACTION_MANAGE_VOICE_KEYPHRASES = 59 "com.android.intent.action.MANAGE_VOICE_KEYPHRASES"; 60 /** 61 * Intent extra: The intent extra for the specific manage action that needs to be performed. 62 * Possible values are {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, 63 * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} 64 * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL}. 65 */ 66 public static final String EXTRA_VOICE_KEYPHRASE_ACTION = 67 "com.android.intent.extra.VOICE_KEYPHRASE_ACTION"; 68 69 /** 70 * Intent extra: The hint text to be shown on the voice keyphrase management UI. 71 */ 72 public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT = 73 "com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT"; 74 /** 75 * Intent extra: The voice locale to use while managing the keyphrase. 76 */ 77 public static final String EXTRA_VOICE_KEYPHRASE_LOCALE = 78 "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE"; 79 80 private KeyphraseMetadata[] mKeyphrases; 81 private String mEnrollmentPackage; 82 private String mParseError; 83 84 public KeyphraseEnrollmentInfo(PackageManager pm) { 85 // Find the apps that supports enrollment for hotword keyhphrases, 86 // Pick a privileged app and obtain the information about the supported keyphrases 87 // from its metadata. 88 List<ResolveInfo> ris = pm.queryIntentActivities( 89 new Intent(ACTION_MANAGE_VOICE_KEYPHRASES), PackageManager.MATCH_DEFAULT_ONLY); 90 if (ris == null || ris.isEmpty()) { 91 // No application capable of enrolling for voice keyphrases is present. 92 mParseError = "No enrollment application found"; 93 return; 94 } 95 96 boolean found = false; 97 ApplicationInfo ai = null; 98 for (ResolveInfo ri : ris) { 99 try { 100 ai = pm.getApplicationInfo( 101 ri.activityInfo.packageName, PackageManager.GET_META_DATA); 102 if ((ai.flags & ApplicationInfo.FLAG_PRIVILEGED) == 0) { 103 // The application isn't privileged (/system/priv-app). 104 // The enrollment application needs to be a privileged system app. 105 Slog.w(TAG, ai.packageName + "is not a privileged system app"); 106 continue; 107 } 108 if (!Manifest.permission.MANAGE_VOICE_KEYPHRASES.equals(ai.permission)) { 109 // The application trying to manage keyphrases doesn't 110 // require the MANAGE_VOICE_KEYPHRASES permission. 111 Slog.w(TAG, ai.packageName + " does not require MANAGE_VOICE_KEYPHRASES"); 112 continue; 113 } 114 mEnrollmentPackage = ai.packageName; 115 found = true; 116 break; 117 } catch (PackageManager.NameNotFoundException e) { 118 Slog.w(TAG, "error parsing voice enrollment meta-data", e); 119 } 120 } 121 122 if (!found) { 123 mKeyphrases = null; 124 mParseError = "No suitable enrollment application found"; 125 return; 126 } 127 128 XmlResourceParser parser = null; 129 try { 130 parser = ai.loadXmlMetaData(pm, VOICE_KEYPHRASE_META_DATA); 131 if (parser == null) { 132 mParseError = "No " + VOICE_KEYPHRASE_META_DATA + " meta-data for " 133 + ai.packageName; 134 return; 135 } 136 137 Resources res = pm.getResourcesForApplication(ai); 138 AttributeSet attrs = Xml.asAttributeSet(parser); 139 140 int type; 141 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 142 && type != XmlPullParser.START_TAG) { 143 } 144 145 String nodeName = parser.getName(); 146 if (!"voice-enrollment-application".equals(nodeName)) { 147 mParseError = "Meta-data does not start with voice-enrollment-application tag"; 148 return; 149 } 150 151 TypedArray array = res.obtainAttributes(attrs, 152 com.android.internal.R.styleable.VoiceEnrollmentApplication); 153 int searchKeyphraseId = array.getInt( 154 com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId, 155 -1); 156 if (searchKeyphraseId != -1) { 157 String searchKeyphrase = array.getString(com.android.internal.R.styleable 158 .VoiceEnrollmentApplication_searchKeyphrase); 159 if (searchKeyphrase == null) { 160 searchKeyphrase = ""; 161 } 162 String searchKeyphraseSupportedLocales = 163 array.getString(com.android.internal.R.styleable 164 .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales); 165 String[] supportedLocales = new String[0]; 166 // Get all the supported locales from the comma-delimted string. 167 if (searchKeyphraseSupportedLocales != null 168 && !searchKeyphraseSupportedLocales.isEmpty()) { 169 supportedLocales = searchKeyphraseSupportedLocales.split(","); 170 } 171 int recognitionModes = array.getInt(com.android.internal.R.styleable 172 .VoiceEnrollmentApplication_searchKeyphraseRecognitionFlags, 0); 173 mKeyphrases = new KeyphraseMetadata[1]; 174 mKeyphrases[0] = new KeyphraseMetadata( 175 searchKeyphraseId, searchKeyphrase, supportedLocales, recognitionModes); 176 } else { 177 mParseError = "searchKeyphraseId not specified in meta-data"; 178 return; 179 } 180 } catch (XmlPullParserException e) { 181 mParseError = "Error parsing keyphrase enrollment meta-data: " + e; 182 Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); 183 return; 184 } catch (IOException e) { 185 mParseError = "Error parsing keyphrase enrollment meta-data: " + e; 186 Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); 187 return; 188 } catch (PackageManager.NameNotFoundException e) { 189 mParseError = "Error parsing keyphrase enrollment meta-data: " + e; 190 Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); 191 return; 192 } finally { 193 if (parser != null) parser.close(); 194 } 195 } 196 197 public String getParseError() { 198 return mParseError; 199 } 200 201 /** 202 * @return An array of available keyphrases that can be enrolled on the system. 203 * It may be null if no keyphrases can be enrolled. 204 */ 205 public KeyphraseMetadata[] listKeyphraseMetadata() { 206 return mKeyphrases; 207 } 208 209 /** 210 * Returns an intent to launch an activity that manages the given keyphrase 211 * for the locale. 212 * 213 * @param action The enrollment related action that this intent is supposed to perform. 214 * This can be one of {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, 215 * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} 216 * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL} 217 * @param keyphrase The keyphrase that the user needs to be enrolled to. 218 * @param locale The locale for which the enrollment needs to be performed. 219 * This is a Java locale, for example "en_US". 220 * @return An {@link Intent} to manage the keyphrase. This can be null if managing the 221 * given keyphrase/locale combination isn't possible. 222 */ 223 public Intent getManageKeyphraseIntent(int action, String keyphrase, String locale) { 224 if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) { 225 Slog.w(TAG, "No enrollment application exists"); 226 return null; 227 } 228 229 if (getKeyphraseMetadata(keyphrase, locale) != null) { 230 Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES) 231 .setPackage(mEnrollmentPackage) 232 .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase) 233 .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale) 234 .putExtra(EXTRA_VOICE_KEYPHRASE_ACTION, action); 235 return intent; 236 } 237 return null; 238 } 239 240 /** 241 * Gets the {@link KeyphraseMetadata} for the given keyphrase and locale, null if any metadata 242 * isn't available for the given combination. 243 * 244 * @param keyphrase The keyphrase that the user needs to be enrolled to. 245 * @param locale The locale for which the enrollment needs to be performed. 246 * This is a Java locale, for example "en_US". 247 * @return The metadata, if the enrollment client supports the given keyphrase 248 * and locale, null otherwise. 249 */ 250 public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) { 251 if (mKeyphrases == null || mKeyphrases.length == 0) { 252 Slog.w(TAG, "Enrollment application doesn't support keyphrases"); 253 return null; 254 } 255 for (KeyphraseMetadata keyphraseMetadata : mKeyphrases) { 256 // Check if the given keyphrase is supported in the locale provided by 257 // the enrollment application. 258 if (keyphraseMetadata.supportsPhrase(keyphrase) 259 && keyphraseMetadata.supportsLocale(locale)) { 260 return keyphraseMetadata; 261 } 262 } 263 Slog.w(TAG, "Enrollment application doesn't support the given keyphrase/locale"); 264 return null; 265 } 266} 267