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 android.service.voice; 18 19 import android.annotation.SdkConstant; 20 import android.app.Service; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.Message; 29 import android.os.RemoteException; 30 import android.os.ServiceManager; 31 import android.provider.Settings; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.internal.app.IVoiceInteractionManagerService; 35 36 import java.io.FileDescriptor; 37 import java.io.PrintWriter; 38 import java.util.Locale; 39 40 /** 41 * Top-level service of the current global voice interactor, which is providing 42 * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. 43 * The current VoiceInteractionService that has been selected by the user is kept 44 * always running by the system, to allow it to do things like listen for hotwords 45 * in the background to instigate voice interactions. 46 * 47 * <p>Because this service is always running, it should be kept as lightweight as 48 * possible. Heavy-weight operations (including showing UI) should be implemented 49 * in the associated {@link android.service.voice.VoiceInteractionSessionService} when 50 * an actual voice interaction is taking place, and that service should run in a 51 * separate process from this one. 52 */ 53 public class VoiceInteractionService extends Service { 54 /** 55 * The {@link Intent} that must be declared as handled by the service. 56 * To be supported, the service must also require the 57 * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so 58 * that other applications can not abuse it. 59 */ 60 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 61 public static final String SERVICE_INTERFACE = 62 "android.service.voice.VoiceInteractionService"; 63 64 /** 65 * Name under which a VoiceInteractionService component publishes information about itself. 66 * This meta-data should reference an XML resource containing a 67 * <code><{@link 68 * android.R.styleable#VoiceInteractionService voice-interaction-service}></code> tag. 69 */ 70 public static final String SERVICE_META_DATA = "android.voice_interaction"; 71 72 IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { 73 @Override public void ready() { 74 mHandler.sendEmptyMessage(MSG_READY); 75 } 76 @Override public void shutdown() { 77 mHandler.sendEmptyMessage(MSG_SHUTDOWN); 78 } 79 @Override public void soundModelsChanged() { 80 mHandler.sendEmptyMessage(MSG_SOUND_MODELS_CHANGED); 81 } 82 @Override 83 public void launchVoiceAssistFromKeyguard() throws RemoteException { 84 mHandler.sendEmptyMessage(MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD); 85 } 86 }; 87 88 MyHandler mHandler; 89 90 IVoiceInteractionManagerService mSystemService; 91 92 private final Object mLock = new Object(); 93 94 private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; 95 96 private AlwaysOnHotwordDetector mHotwordDetector; 97 98 static final int MSG_READY = 1; 99 static final int MSG_SHUTDOWN = 2; 100 static final int MSG_SOUND_MODELS_CHANGED = 3; 101 static final int MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD = 4; 102 103 class MyHandler extends Handler { 104 @Override handleMessage(Message msg)105 public void handleMessage(Message msg) { 106 switch (msg.what) { 107 case MSG_READY: 108 onReady(); 109 break; 110 case MSG_SHUTDOWN: 111 onShutdownInternal(); 112 break; 113 case MSG_SOUND_MODELS_CHANGED: 114 onSoundModelsChangedInternal(); 115 break; 116 case MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD: 117 onLaunchVoiceAssistFromKeyguard(); 118 break; 119 default: 120 super.handleMessage(msg); 121 } 122 } 123 } 124 125 /** 126 * Called when a user has activated an affordance to launch voice assist from the Keyguard. 127 * 128 * <p>This method will only be called if the VoiceInteractionService has set 129 * {@link android.R.attr#supportsLaunchVoiceAssistFromKeyguard} and the Keyguard is showing.</p> 130 * 131 * <p>A valid implementation must start a new activity that should use {@link 132 * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display 133 * on top of the lock screen.</p> 134 */ onLaunchVoiceAssistFromKeyguard()135 public void onLaunchVoiceAssistFromKeyguard() { 136 } 137 138 /** 139 * Check whether the given service component is the currently active 140 * VoiceInteractionService. 141 */ isActiveService(Context context, ComponentName service)142 public static boolean isActiveService(Context context, ComponentName service) { 143 String cur = Settings.Secure.getString(context.getContentResolver(), 144 Settings.Secure.VOICE_INTERACTION_SERVICE); 145 if (cur == null || cur.isEmpty()) { 146 return false; 147 } 148 ComponentName curComp = ComponentName.unflattenFromString(cur); 149 if (curComp == null) { 150 return false; 151 } 152 return curComp.equals(service); 153 } 154 155 /** 156 * Set contextual options you would always like to have disabled when a session 157 * is shown. The flags may be any combination of 158 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 159 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 160 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}. 161 */ setDisabledShowContext(int flags)162 public void setDisabledShowContext(int flags) { 163 try { 164 mSystemService.setDisabledShowContext(flags); 165 } catch (RemoteException e) { 166 } 167 } 168 169 /** 170 * Return the value set by {@link #setDisabledShowContext}. 171 */ getDisabledShowContext()172 public int getDisabledShowContext() { 173 try { 174 return mSystemService.getDisabledShowContext(); 175 } catch (RemoteException e) { 176 return 0; 177 } 178 } 179 180 /** 181 * Request that the associated {@link android.service.voice.VoiceInteractionSession} be 182 * shown to the user, starting it if necessary. 183 * @param args Arbitrary arguments that will be propagated to the session. 184 * @param flags Indicates additional optional behavior that should be performed. May 185 * be any combination of 186 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 187 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 188 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT} 189 * to request that the system generate and deliver assist data on the current foreground 190 * app as part of showing the session UI. 191 */ showSession(Bundle args, int flags)192 public void showSession(Bundle args, int flags) { 193 if (mSystemService == null) { 194 throw new IllegalStateException("Not available until onReady() is called"); 195 } 196 try { 197 mSystemService.showSession(mInterface, args, flags); 198 } catch (RemoteException e) { 199 } 200 } 201 202 @Override onCreate()203 public void onCreate() { 204 super.onCreate(); 205 mHandler = new MyHandler(); 206 } 207 208 @Override onBind(Intent intent)209 public IBinder onBind(Intent intent) { 210 if (SERVICE_INTERFACE.equals(intent.getAction())) { 211 return mInterface.asBinder(); 212 } 213 return null; 214 } 215 216 /** 217 * Called during service initialization to tell you when the system is ready 218 * to receive interaction from it. You should generally do initialization here 219 * rather than in {@link #onCreate}. Methods such as {@link #showSession} and 220 * {@link #createAlwaysOnHotwordDetector} 221 * will not be operational until this point. 222 */ onReady()223 public void onReady() { 224 mSystemService = IVoiceInteractionManagerService.Stub.asInterface( 225 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); 226 mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); 227 } 228 onShutdownInternal()229 private void onShutdownInternal() { 230 onShutdown(); 231 // Stop any active recognitions when shutting down. 232 // This ensures that if implementations forget to stop any active recognition, 233 // It's still guaranteed to have been stopped. 234 // This helps with cases where the voice interaction implementation is changed 235 // by the user. 236 safelyShutdownHotwordDetector(); 237 } 238 239 /** 240 * Called during service de-initialization to tell you when the system is shutting the 241 * service down. 242 * At this point this service may no longer be the active {@link VoiceInteractionService}. 243 */ onShutdown()244 public void onShutdown() { 245 } 246 onSoundModelsChangedInternal()247 private void onSoundModelsChangedInternal() { 248 synchronized (this) { 249 if (mHotwordDetector != null) { 250 // TODO: Stop recognition if a sound model that was being recognized gets deleted. 251 mHotwordDetector.onSoundModelsChanged(); 252 } 253 } 254 } 255 256 /** 257 * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. 258 * This instance must be retained and used by the client. 259 * Calling this a second time invalidates the previously created hotword detector 260 * which can no longer be used to manage recognition. 261 * 262 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 263 * @param locale The locale for which the enrollment needs to be performed. 264 * @param callback The callback to notify of detection events. 265 * @return An always-on hotword detector for the given keyphrase and locale. 266 */ createAlwaysOnHotwordDetector( String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback)267 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 268 String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) { 269 if (mSystemService == null) { 270 throw new IllegalStateException("Not available until onReady() is called"); 271 } 272 synchronized (mLock) { 273 // Allow only one concurrent recognition via the APIs. 274 safelyShutdownHotwordDetector(); 275 mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback, 276 mKeyphraseEnrollmentInfo, mInterface, mSystemService); 277 } 278 return mHotwordDetector; 279 } 280 281 /** 282 * @return Details of keyphrases available for enrollment. 283 * @hide 284 */ 285 @VisibleForTesting getKeyphraseEnrollmentInfo()286 protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() { 287 return mKeyphraseEnrollmentInfo; 288 } 289 safelyShutdownHotwordDetector()290 private void safelyShutdownHotwordDetector() { 291 try { 292 synchronized (mLock) { 293 if (mHotwordDetector != null) { 294 mHotwordDetector.stopRecognition(); 295 mHotwordDetector.invalidate(); 296 mHotwordDetector = null; 297 } 298 } 299 } catch (Exception ex) { 300 // Ignore. 301 } 302 } 303 304 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)305 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 306 pw.println("VOICE INTERACTION"); 307 synchronized (mLock) { 308 pw.println(" AlwaysOnHotwordDetector"); 309 if (mHotwordDetector == null) { 310 pw.println(" NULL"); 311 } else { 312 mHotwordDetector.dump(" ", pw); 313 } 314 } 315 } 316 } 317