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  * 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>&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         @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
    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      */
    135     public void onLaunchVoiceAssistFromKeyguard() {
    136     }
    137 
    138     /**
    139      * 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         }
    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      */
    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     /**
    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      */
    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
    203     public void onCreate() {
    204         super.onCreate();
    205         mHandler = new MyHandler();
    206     }
    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     }
    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      */
    223     public void onReady() {
    224         mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
    225                 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
    226         mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager());
    227     }
    228 
    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      */
    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         }
    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      */
    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
    286     protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() {
    287         return mKeyphraseEnrollmentInfo;
    288     }
    289 
    290     /**
    291       * Checks if a given keyphrase and locale are supported to create an
    292       * {@link AlwaysOnHotwordDetector}.
    293       *
    294       * @return true if the keyphrase and locale combination is supported, false otherwise.
    295       * @hide
    296       */
    297     public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) {
    298         if (mKeyphraseEnrollmentInfo == null) {
    299             return false;
    300         }
    301         return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
    302     }
    303 
    304     private void safelyShutdownHotwordDetector() {
    305         try {
    306             synchronized (mLock) {
    307                 if (mHotwordDetector != null) {
    308                     mHotwordDetector.stopRecognition();
    309                     mHotwordDetector.invalidate();
    310                     mHotwordDetector = null;
    311                 }
    312             }
    313         } catch (Exception ex) {
    314             // Ignore.
    315         }
    316     }
    317 
    318     @Override
    319     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    320         pw.println("VOICE INTERACTION");
    321         synchronized (mLock) {
    322             pw.println("  AlwaysOnHotwordDetector");
    323             if (mHotwordDetector == null) {
    324                 pw.println("    NULL");
    325             } else {
    326                 mHotwordDetector.dump("    ", pw);
    327             }
    328         }
    329     }
    330 }
    331