KeyphraseEnrollmentInfo.java revision d7018200312e4e4dc3f67cf33dc90bf7ce585844
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 String searchKeyphraseSupportedLocales = 160 array.getString(com.android.internal.R.styleable 161 .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales); 162 String[] supportedLocales = new String[0]; 163 // Get all the supported locales from the comma-delimted string. 164 if (searchKeyphraseSupportedLocales != null 165 && !searchKeyphraseSupportedLocales.isEmpty()) { 166 supportedLocales = searchKeyphraseSupportedLocales.split(","); 167 } 168 mKeyphrases = new KeyphraseMetadata[1]; 169 mKeyphrases[0] = new KeyphraseMetadata( 170 searchKeyphraseId, searchKeyphrase, supportedLocales); 171 } else { 172 mParseError = "searchKeyphraseId not specified in meta-data"; 173 return; 174 } 175 } catch (XmlPullParserException e) { 176 mParseError = "Error parsing keyphrase enrollment meta-data: " + e; 177 Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); 178 return; 179 } catch (IOException e) { 180 mParseError = "Error parsing keyphrase enrollment meta-data: " + e; 181 Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); 182 return; 183 } catch (PackageManager.NameNotFoundException e) { 184 mParseError = "Error parsing keyphrase enrollment meta-data: " + e; 185 Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); 186 return; 187 } finally { 188 if (parser != null) parser.close(); 189 } 190 } 191 192 public String getParseError() { 193 return mParseError; 194 } 195 196 /** 197 * @return An array of available keyphrases that can be enrolled on the system. 198 * It may be null if no keyphrases can be enrolled. 199 */ 200 public KeyphraseMetadata[] listKeyphraseMetadata() { 201 return mKeyphrases; 202 } 203 204 /** 205 * Returns an intent to launch an activity that manages the given keyphrase 206 * for the locale. 207 * 208 * @param action The enrollment related action that this intent is supposed to perform. 209 * This can be one of {@link AlwaysOnHotwordDetector#MANAGE_ACTION_ENROLL}, 210 * {@link AlwaysOnHotwordDetector#MANAGE_ACTION_RE_ENROLL} 211 * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL} 212 * @param keyphrase The keyphrase that the user needs to be enrolled to. 213 * @param locale The locale for which the enrollment needs to be performed. 214 * This is a Java locale, for example "en_US". 215 * @return An {@link Intent} to manage the keyphrase. This can be null if managing the 216 * given keyphrase/locale combination isn't possible. 217 */ 218 public Intent getManageKeyphraseIntent(int action, String keyphrase, String locale) { 219 if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) { 220 Slog.w(TAG, "No enrollment application exists"); 221 return null; 222 } 223 224 if (getKeyphraseMetadata(keyphrase, locale) != null) { 225 Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES) 226 .setPackage(mEnrollmentPackage) 227 .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase) 228 .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale) 229 .putExtra(EXTRA_VOICE_KEYPHRASE_ACTION, action); 230 return intent; 231 } 232 return null; 233 } 234 235 /** 236 * Gets the {@link KeyphraseMetadata} for the given keyphrase and locale, null if any metadata 237 * isn't available for the given combination. 238 * 239 * @param keyphrase The keyphrase that the user needs to be enrolled to. 240 * @param locale The locale for which the enrollment needs to be performed. 241 * This is a Java locale, for example "en_US". 242 * @return true, if an enrollment client supports the given keyphrase and the given locale. 243 */ 244 public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) { 245 if (mKeyphrases == null || mKeyphrases.length == 0) { 246 Slog.w(TAG, "Enrollment application doesn't support keyphrases"); 247 return null; 248 } 249 for (KeyphraseMetadata keyphraseMetadata : mKeyphrases) { 250 // Check if the given keyphrase is supported in the locale provided by 251 // the enrollment application. 252 if (keyphraseMetadata.supportsPhrase(keyphrase) 253 && keyphraseMetadata.supportsLocale(locale)) { 254 return keyphraseMetadata; 255 } 256 } 257 Slog.w(TAG, "Enrollment application doesn't support the given keyphrase/locale"); 258 return null; 259 } 260} 261