Home | History | Annotate | Download | only in voice
      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 /**
     42  * Top-level service of the current global voice interactor, which is providing
     43  * support for hotwording etc.
     44  * The current VoiceInteractionService that has been selected by the user is kept
     45  * always running by the system, to allow it to do things like listen for hotwords
     46  * in the background.
     47  *
     48  * <p>Because this service is always running, it should be kept as lightweight as
     49  * possible.  Heavy-weight operations (including showing UI) should be implemented
     50  * in the associated {@link android.service.voice.VoiceInteractionSessionService}
     51  * that only runs while the operation is active.
     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>&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() {
     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     };
     83 
     84     MyHandler mHandler;
     85 
     86     IVoiceInteractionManagerService mSystemService;
     87 
     88     private final Object mLock = new Object();
     89 
     90     private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
     91 
     92     private AlwaysOnHotwordDetector mHotwordDetector;
     93 
     94     static final int MSG_READY = 1;
     95     static final int MSG_SHUTDOWN = 2;
     96     static final int MSG_SOUND_MODELS_CHANGED = 3;
     97 
     98     class MyHandler extends Handler {
     99         @Override
    100         public void handleMessage(Message msg) {
    101             switch (msg.what) {
    102                 case MSG_READY:
    103                     onReady();
    104                     break;
    105                 case MSG_SHUTDOWN:
    106                     onShutdownInternal();
    107                     break;
    108                 case MSG_SOUND_MODELS_CHANGED:
    109                     onSoundModelsChangedInternal();
    110                     break;
    111                 default:
    112                     super.handleMessage(msg);
    113             }
    114         }
    115     }
    116 
    117     /**
    118      * Check whether the given service component is the currently active
    119      * VoiceInteractionService.
    120      */
    121     public static boolean isActiveService(Context context, ComponentName service) {
    122         String cur = Settings.Secure.getString(context.getContentResolver(),
    123                 Settings.Secure.VOICE_INTERACTION_SERVICE);
    124         if (cur == null || cur.isEmpty()) {
    125             return false;
    126         }
    127         ComponentName curComp = ComponentName.unflattenFromString(cur);
    128         if (curComp == null) {
    129             return false;
    130         }
    131         return curComp.equals(service);
    132     }
    133 
    134     /**
    135      * Initiate the execution of a new {@link android.service.voice.VoiceInteractionSession}.
    136      * @param args Arbitrary arguments that will be propagated to the session.
    137      */
    138     public void startSession(Bundle args) {
    139         if (mSystemService == null) {
    140             throw new IllegalStateException("Not available until onReady() is called");
    141         }
    142         try {
    143             mSystemService.startSession(mInterface, args);
    144         } catch (RemoteException e) {
    145         }
    146     }
    147 
    148     @Override
    149     public void onCreate() {
    150         super.onCreate();
    151         mHandler = new MyHandler();
    152     }
    153 
    154     @Override
    155     public IBinder onBind(Intent intent) {
    156         if (SERVICE_INTERFACE.equals(intent.getAction())) {
    157             return mInterface.asBinder();
    158         }
    159         return null;
    160     }
    161 
    162     /**
    163      * Called during service initialization to tell you when the system is ready
    164      * to receive interaction from it. You should generally do initialization here
    165      * rather than in {@link #onCreate()}. Methods such as {@link #startSession(Bundle)} and
    166      * {@link #createAlwaysOnHotwordDetector(String, Locale, android.service.voice.AlwaysOnHotwordDetector.Callback)}
    167      * will not be operational until this point.
    168      */
    169     public void onReady() {
    170         mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
    171                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
    172         mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
    173     }
    174 
    175     private void onShutdownInternal() {
    176         onShutdown();
    177         // Stop any active recognitions when shutting down.
    178         // This ensures that if implementations forget to stop any active recognition,
    179         // It's still guaranteed to have been stopped.
    180         // This helps with cases where the voice interaction implementation is changed
    181         // by the user.
    182         safelyShutdownHotwordDetector();
    183     }
    184 
    185     /**
    186      * Called during service de-initialization to tell you when the system is shutting the
    187      * service down.
    188      * At this point this service may no longer be the active {@link VoiceInteractionService}.
    189      */
    190     public void onShutdown() {
    191     }
    192 
    193     private void onSoundModelsChangedInternal() {
    194         synchronized (this) {
    195             if (mHotwordDetector != null) {
    196                 // TODO: Stop recognition if a sound model that was being recognized gets deleted.
    197                 mHotwordDetector.onSoundModelsChanged();
    198             }
    199         }
    200     }
    201 
    202     /**
    203      * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale.
    204      * This instance must be retained and used by the client.
    205      * Calling this a second time invalidates the previously created hotword detector
    206      * which can no longer be used to manage recognition.
    207      *
    208      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
    209      * @param locale The locale for which the enrollment needs to be performed.
    210      * @param callback The callback to notify of detection events.
    211      * @return An always-on hotword detector for the given keyphrase and locale.
    212      */
    213     public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
    214             String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) {
    215         if (mSystemService == null) {
    216             throw new IllegalStateException("Not available until onReady() is called");
    217         }
    218         synchronized (mLock) {
    219             // Allow only one concurrent recognition via the APIs.
    220             safelyShutdownHotwordDetector();
    221             mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
    222                     mKeyphraseEnrollmentInfo, mInterface, mSystemService);
    223         }
    224         return mHotwordDetector;
    225     }
    226 
    227     /**
    228      * @return Details of keyphrases available for enrollment.
    229      * @hide
    230      */
    231     @VisibleForTesting
    232     protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
    233         return mKeyphraseEnrollmentInfo;
    234     }
    235 
    236     private void safelyShutdownHotwordDetector() {
    237         try {
    238             synchronized (mLock) {
    239                 if (mHotwordDetector != null) {
    240                     mHotwordDetector.stopRecognition();
    241                     mHotwordDetector.invalidate();
    242                     mHotwordDetector = null;
    243                 }
    244             }
    245         } catch (Exception ex) {
    246             // Ignore.
    247         }
    248     }
    249 
    250     @Override
    251     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    252         pw.println("VOICE INTERACTION");
    253         synchronized (mLock) {
    254             pw.println("  AlwaysOnHotwordDetector");
    255             if (mHotwordDetector == null) {
    256                 pw.println("    NULL");
    257             } else {
    258                 mHotwordDetector.dump("    ", pw);
    259             }
    260         }
    261     }
    262 }
    263