blob: fbc6fc6c3cd69dcc59c1246d02f39039f088d30d [file] [log] [blame]
Dianne Hackborn91097de2014-04-04 18:02:06 -07001/**
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.service.voice;
18
19import android.annotation.SdkConstant;
Dianne Hackborn91097de2014-04-04 18:02:06 -070020import android.app.Service;
Dianne Hackbornfee756f2014-07-16 17:31:10 -070021import android.content.ComponentName;
Dianne Hackborn91097de2014-04-04 18:02:06 -070022import android.content.Context;
23import android.content.Intent;
Sandeepd7018202014-07-10 15:15:39 -070024import android.hardware.soundtrigger.KeyphraseEnrollmentInfo;
Dianne Hackborn18f0d352014-04-25 17:06:18 -070025import android.os.Bundle;
Dianne Hackbornfee756f2014-07-16 17:31:10 -070026import android.os.Handler;
Dianne Hackborn91097de2014-04-04 18:02:06 -070027import android.os.IBinder;
Dianne Hackbornfee756f2014-07-16 17:31:10 -070028import android.os.Message;
Dianne Hackborn91097de2014-04-04 18:02:06 -070029import android.os.RemoteException;
30import android.os.ServiceManager;
Dianne Hackbornfee756f2014-07-16 17:31:10 -070031import android.provider.Settings;
Sandeep Siddhartha6daae962014-07-21 10:31:34 -070032
Sandeep Siddhartha22968952014-06-10 12:32:53 -070033import com.android.internal.annotations.VisibleForTesting;
Dianne Hackborn91097de2014-04-04 18:02:06 -070034import com.android.internal.app.IVoiceInteractionManagerService;
35
Sandeep Siddhartha6df952e2014-08-08 15:28:46 -070036import java.io.FileDescriptor;
37import java.io.PrintWriter;
Sandeep Siddharthadcf30682014-08-22 16:36:34 -070038import java.util.Locale;
Sandeep Siddhartha6df952e2014-08-08 15:28:46 -070039
Dianne Hackbornc03c9162014-05-02 10:45:59 -070040/**
41 * Top-level service of the current global voice interactor, which is providing
Dianne Hackborn4e106ce2015-01-14 15:15:34 -080042 * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
Dianne Hackbornc03c9162014-05-02 10:45:59 -070043 * 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
Dianne Hackborn4e106ce2015-01-14 15:15:34 -080045 * in the background to instigate voice interactions.
Dianne Hackbornc03c9162014-05-02 10:45:59 -070046 *
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
Dianne Hackborn4e106ce2015-01-14 15:15:34 -080049 * 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.
Dianne Hackbornc03c9162014-05-02 10:45:59 -070052 */
Dianne Hackborn91097de2014-04-04 18:02:06 -070053public 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>&lt;{@link
68 * android.R.styleable#VoiceInteractionService voice-interaction-service}&gt;</code> tag.
69 */
70 public static final String SERVICE_META_DATA = "android.voice_interaction";
71
72 IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
Dianne Hackbornfee756f2014-07-16 17:31:10 -070073 @Override public void ready() {
74 mHandler.sendEmptyMessage(MSG_READY);
75 }
Sandeep Siddhartha6daae962014-07-21 10:31:34 -070076 @Override public void shutdown() {
77 mHandler.sendEmptyMessage(MSG_SHUTDOWN);
78 }
79 @Override public void soundModelsChanged() {
80 mHandler.sendEmptyMessage(MSG_SOUND_MODELS_CHANGED);
81 }
Selim Cineke70d6532015-04-24 16:46:13 -070082 @Override
83 public void launchVoiceAssistFromKeyguard() throws RemoteException {
84 mHandler.sendEmptyMessage(MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD);
85 }
Dianne Hackborn91097de2014-04-04 18:02:06 -070086 };
87
Dianne Hackbornfee756f2014-07-16 17:31:10 -070088 MyHandler mHandler;
89
Dianne Hackborn91097de2014-04-04 18:02:06 -070090 IVoiceInteractionManagerService mSystemService;
91
Sandeep Siddhartha6daae962014-07-21 10:31:34 -070092 private final Object mLock = new Object();
93
Sandeep Siddharthae912ac02014-06-03 16:29:37 -070094 private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
95
Sandeep Siddhartha6daae962014-07-21 10:31:34 -070096 private AlwaysOnHotwordDetector mHotwordDetector;
97
Dianne Hackbornfee756f2014-07-16 17:31:10 -070098 static final int MSG_READY = 1;
Sandeep Siddhartha6daae962014-07-21 10:31:34 -070099 static final int MSG_SHUTDOWN = 2;
100 static final int MSG_SOUND_MODELS_CHANGED = 3;
Selim Cineke70d6532015-04-24 16:46:13 -0700101 static final int MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD = 4;
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700102
103 class MyHandler extends Handler {
104 @Override
105 public void handleMessage(Message msg) {
106 switch (msg.what) {
107 case MSG_READY:
108 onReady();
109 break;
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700110 case MSG_SHUTDOWN:
111 onShutdownInternal();
112 break;
113 case MSG_SOUND_MODELS_CHANGED:
114 onSoundModelsChangedInternal();
115 break;
Selim Cineke70d6532015-04-24 16:46:13 -0700116 case MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD:
117 onLaunchVoiceAssistFromKeyguard();
118 break;
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700119 default:
120 super.handleMessage(msg);
121 }
122 }
123 }
124
125 /**
Selim Cineke70d6532015-04-24 16:46:13 -0700126 * 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 */
135 public void onLaunchVoiceAssistFromKeyguard() {
136 }
137
138 /**
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700139 * Check whether the given service component is the currently active
140 * VoiceInteractionService.
141 */
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 }
Barnaby Jamese2c020a42014-08-11 15:13:42 -0700152 return curComp.equals(service);
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700153 }
154
155 /**
Dianne Hackborn1de11862015-07-15 14:20:51 -0700156 * 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 */
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 */
172 public int getDisabledShowContext() {
173 try {
174 return mSystemService.getDisabledShowContext();
175 } catch (RemoteException e) {
176 return 0;
177 }
178 }
179
180 /**
Dianne Hackbornffeecb12015-02-25 11:08:11 -0800181 * Request that the associated {@link android.service.voice.VoiceInteractionSession} be
182 * shown to the user, starting it if necessary.
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700183 * @param args Arbitrary arguments that will be propagated to the session.
Dianne Hackborn2ee5c362015-05-29 17:58:53 -0700184 * @param flags Indicates additional optional behavior that should be performed. May
Dianne Hackborn1de11862015-07-15 14:20:51 -0700185 * 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}
Dianne Hackborn2ee5c362015-05-29 17:58:53 -0700189 * to request that the system generate and deliver assist data on the current foreground
190 * app as part of showing the session UI.
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700191 */
Dianne Hackbornffeecb12015-02-25 11:08:11 -0800192 public void showSession(Bundle args, int flags) {
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700193 if (mSystemService == null) {
194 throw new IllegalStateException("Not available until onReady() is called");
195 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700196 try {
Dianne Hackbornffeecb12015-02-25 11:08:11 -0800197 mSystemService.showSession(mInterface, args, flags);
Dianne Hackborn91097de2014-04-04 18:02:06 -0700198 } catch (RemoteException e) {
199 }
200 }
201
202 @Override
203 public void onCreate() {
204 super.onCreate();
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700205 mHandler = new MyHandler();
Dianne Hackborn91097de2014-04-04 18:02:06 -0700206 }
207
208 @Override
209 public IBinder onBind(Intent intent) {
210 if (SERVICE_INTERFACE.equals(intent.getAction())) {
211 return mInterface.asBinder();
212 }
213 return null;
214 }
Sandeep Siddharthae912ac02014-06-03 16:29:37 -0700215
216 /**
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700217 * Called during service initialization to tell you when the system is ready
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700218 * to receive interaction from it. You should generally do initialization here
Dianne Hackbornffeecb12015-02-25 11:08:11 -0800219 * rather than in {@link #onCreate}. Methods such as {@link #showSession} and
Dianne Hackbornae6688b2015-02-11 17:02:41 -0800220 * {@link #createAlwaysOnHotwordDetector}
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700221 * will not be operational until this point.
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700222 */
223 public void onReady() {
224 mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
225 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
226 mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700227 }
228
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700229 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
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700239 /**
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700240 * Called during service de-initialization to tell you when the system is shutting the
241 * service down.
Sandeep Siddhartha5e33fb02014-08-01 18:09:07 -0700242 * At this point this service may no longer be the active {@link VoiceInteractionService}.
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700243 */
244 public void onShutdown() {
245 }
246
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 }
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700254 }
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 *
Sandeepd7018202014-07-10 15:15:39 -0700262 * @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.
Sandeepd7018202014-07-10 15:15:39 -0700264 * @param callback The callback to notify of detection events.
Chris Thorntonbf8288c2017-05-08 12:38:18 -0700265 * @return An always-on hotword detector for the given keyphrase and locale. Is null if the
266 * keyphrase and locale is not supported.
Sandeep Siddharthae912ac02014-06-03 16:29:37 -0700267 */
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700268 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
Sandeep Siddharthadcf30682014-08-22 16:36:34 -0700269 String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) {
Dianne Hackbornfee756f2014-07-16 17:31:10 -0700270 if (mSystemService == null) {
271 throw new IllegalStateException("Not available until onReady() is called");
272 }
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700273 synchronized (mLock) {
274 // Allow only one concurrent recognition via the APIs.
275 safelyShutdownHotwordDetector();
Chris Thorntonbf8288c2017-05-08 12:38:18 -0700276 if (isKeyphraseAndLocaleSupportedForHotword(keyphrase, locale)) {
277 mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
278 mKeyphraseEnrollmentInfo, mInterface, mSystemService);
279 }
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700280 }
281 return mHotwordDetector;
Sandeep Siddharthae912ac02014-06-03 16:29:37 -0700282 }
Sandeep Siddhartha22968952014-06-10 12:32:53 -0700283
284 /**
285 * @return Details of keyphrases available for enrollment.
286 * @hide
287 */
288 @VisibleForTesting
289 protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
290 return mKeyphraseEnrollmentInfo;
291 }
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700292
Chris Thorntonbf8288c2017-05-08 12:38:18 -0700293 /**
294 * Checks if a given keyphrase and locale are supported to create an
295 * {@link AlwaysOnHotwordDetector}.
296 *
297 * @return true if the keyphrase and locale combination is supported, false otherwise.
298 * @hide
299 */
300 public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) {
301 if (mKeyphraseEnrollmentInfo == null) {
302 return false;
303 }
304 return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
305 }
306
Sandeep Siddhartha6daae962014-07-21 10:31:34 -0700307 private void safelyShutdownHotwordDetector() {
308 try {
309 synchronized (mLock) {
310 if (mHotwordDetector != null) {
311 mHotwordDetector.stopRecognition();
312 mHotwordDetector.invalidate();
313 mHotwordDetector = null;
314 }
315 }
316 } catch (Exception ex) {
317 // Ignore.
318 }
319 }
Sandeep Siddhartha6df952e2014-08-08 15:28:46 -0700320
321 @Override
322 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
323 pw.println("VOICE INTERACTION");
324 synchronized (mLock) {
325 pw.println(" AlwaysOnHotwordDetector");
326 if (mHotwordDetector == null) {
327 pw.println(" NULL");
328 } else {
329 mHotwordDetector.dump(" ", pw);
330 }
331 }
332 }
Dianne Hackborn91097de2014-04-04 18:02:06 -0700333}