Home | History | Annotate | Download | only in soundtrigger
      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.media.soundtrigger;
     18 
     19 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
     20 
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.annotation.RequiresPermission;
     24 import android.annotation.SystemApi;
     25 import android.annotation.SystemService;
     26 import android.app.PendingIntent;
     27 import android.content.ComponentName;
     28 import android.content.Context;
     29 import android.hardware.soundtrigger.SoundTrigger;
     30 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
     31 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
     32 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
     33 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
     34 import android.os.Bundle;
     35 import android.os.Handler;
     36 import android.os.ParcelUuid;
     37 import android.os.RemoteException;
     38 import android.provider.Settings;
     39 import android.util.Slog;
     40 
     41 import com.android.internal.app.ISoundTriggerService;
     42 import com.android.internal.util.Preconditions;
     43 
     44 import java.util.HashMap;
     45 import java.util.UUID;
     46 
     47 /**
     48  * This class provides management of non-voice (general sound trigger) based sound recognition
     49  * models. Usage of this class is restricted to system or signature applications only. This allows
     50  * OEMs to write apps that can manage non-voice based sound trigger models.
     51  *
     52  * @hide
     53  */
     54 @SystemApi
     55 @SystemService(Context.SOUND_TRIGGER_SERVICE)
     56 public final class SoundTriggerManager {
     57     private static final boolean DBG = false;
     58     private static final String TAG = "SoundTriggerManager";
     59 
     60     private final Context mContext;
     61     private final ISoundTriggerService mSoundTriggerService;
     62 
     63     // Stores a mapping from the sound model UUID to the SoundTriggerInstance created by
     64     // the createSoundTriggerDetector() call.
     65     private final HashMap<UUID, SoundTriggerDetector> mReceiverInstanceMap;
     66 
     67     /**
     68      * @hide
     69      */
     70     public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService ) {
     71         if (DBG) {
     72             Slog.i(TAG, "SoundTriggerManager created.");
     73         }
     74         mSoundTriggerService = soundTriggerService;
     75         mContext = context;
     76         mReceiverInstanceMap = new HashMap<UUID, SoundTriggerDetector>();
     77     }
     78 
     79     /**
     80      * Updates the given sound trigger model.
     81      */
     82     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     83     public void updateModel(Model model) {
     84         try {
     85             mSoundTriggerService.updateSoundModel(model.getGenericSoundModel());
     86         } catch (RemoteException e) {
     87             throw e.rethrowFromSystemServer();
     88         }
     89     }
     90 
     91     /**
     92      * Returns the sound trigger model represented by the given UUID. An instance of {@link Model}
     93      * is returned.
     94      */
     95     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     96     public Model getModel(UUID soundModelId) {
     97         try {
     98             return new Model(mSoundTriggerService.getSoundModel(
     99                     new ParcelUuid(soundModelId)));
    100         } catch (RemoteException e) {
    101             throw e.rethrowFromSystemServer();
    102         }
    103     }
    104 
    105     /**
    106      * Deletes the sound model represented by the provided UUID.
    107      */
    108     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    109     public void deleteModel(UUID soundModelId) {
    110         try {
    111             mSoundTriggerService.deleteSoundModel(new ParcelUuid(soundModelId));
    112         } catch (RemoteException e) {
    113             throw e.rethrowFromSystemServer();
    114         }
    115     }
    116 
    117     /**
    118      * Creates an instance of {@link SoundTriggerDetector} which can be used to start/stop
    119      * recognition on the model and register for triggers from the model. Note that this call
    120      * invalidates any previously returned instances for the same sound model Uuid.
    121      *
    122      * @param soundModelId UUID of the sound model to create the receiver object for.
    123      * @param callback Instance of the {@link SoundTriggerDetector#Callback} object for the
    124      * callbacks for the given sound model.
    125      * @param handler The Handler to use for the callback operations. A null value will use the
    126      * current thread's Looper.
    127      * @return Instance of {@link SoundTriggerDetector} or null on error.
    128      */
    129     @Nullable
    130     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    131     public SoundTriggerDetector createSoundTriggerDetector(UUID soundModelId,
    132             @NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler) {
    133         if (soundModelId == null) {
    134             return null;
    135         }
    136 
    137         SoundTriggerDetector oldInstance = mReceiverInstanceMap.get(soundModelId);
    138         if (oldInstance != null) {
    139             // Shutdown old instance.
    140         }
    141         SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerService,
    142                 soundModelId, callback, handler);
    143         mReceiverInstanceMap.put(soundModelId, newInstance);
    144         return newInstance;
    145     }
    146 
    147     /**
    148      * Class captures the data and fields that represent a non-keyphrase sound model. Use the
    149      * factory constructor {@link Model#create()} to create an instance.
    150      */
    151     // We use encapsulation to expose the SoundTrigger.GenericSoundModel as a SystemApi. This
    152     // prevents us from exposing SoundTrigger.GenericSoundModel as an Api.
    153     public static class Model {
    154 
    155         private SoundTrigger.GenericSoundModel mGenericSoundModel;
    156 
    157         /**
    158          * @hide
    159          */
    160         Model(SoundTrigger.GenericSoundModel soundTriggerModel) {
    161             mGenericSoundModel = soundTriggerModel;
    162         }
    163 
    164         /**
    165          * Factory constructor to create a SoundModel instance for use with methods in this
    166          * class.
    167          */
    168         public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) {
    169             return new Model(new SoundTrigger.GenericSoundModel(modelUuid,
    170                         vendorUuid, data));
    171         }
    172 
    173         public UUID getModelUuid() {
    174             return mGenericSoundModel.uuid;
    175         }
    176 
    177         public UUID getVendorUuid() {
    178             return mGenericSoundModel.vendorUuid;
    179         }
    180 
    181         public byte[] getModelData() {
    182             return mGenericSoundModel.data;
    183         }
    184 
    185         /**
    186          * @hide
    187          */
    188         SoundTrigger.GenericSoundModel getGenericSoundModel() {
    189             return mGenericSoundModel;
    190         }
    191     }
    192 
    193 
    194     /**
    195      * Default message type.
    196      * @hide
    197      */
    198     public static final int FLAG_MESSAGE_TYPE_UNKNOWN = -1;
    199     /**
    200      * Contents of EXTRA_MESSAGE_TYPE extra for a RecognitionEvent.
    201      * @hide
    202      */
    203     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_EVENT = 0;
    204     /**
    205      * Contents of EXTRA_MESSAGE_TYPE extra for recognition error events.
    206      * @hide
    207      */
    208     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_ERROR = 1;
    209     /**
    210      * Contents of EXTRA_MESSAGE_TYPE extra for a recognition paused events.
    211      * @hide
    212      */
    213     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED = 2;
    214     /**
    215      * Contents of EXTRA_MESSAGE_TYPE extra for recognition resumed events.
    216      * @hide
    217      */
    218     public static final int FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED = 3;
    219 
    220     /**
    221      * Extra key in the intent for the type of the message.
    222      * @hide
    223      */
    224     public static final String EXTRA_MESSAGE_TYPE = "android.media.soundtrigger.MESSAGE_TYPE";
    225     /**
    226      * Extra key in the intent that holds the RecognitionEvent parcelable.
    227      * @hide
    228      */
    229     public static final String EXTRA_RECOGNITION_EVENT = "android.media.soundtrigger.RECOGNITION_EVENT";
    230     /**
    231      * Extra key in the intent that holds the status in an error message.
    232      * @hide
    233      */
    234     public static final String EXTRA_STATUS = "android.media.soundtrigger.STATUS";
    235 
    236     /**
    237      * Loads a given sound model into the sound trigger. Note the model will be unloaded if there is
    238      * an error/the system service is restarted.
    239      * @hide
    240      */
    241     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    242     public int loadSoundModel(SoundModel soundModel) {
    243         if (soundModel == null) {
    244             return STATUS_ERROR;
    245         }
    246 
    247         try {
    248             switch (soundModel.type) {
    249                 case SoundModel.TYPE_GENERIC_SOUND:
    250                     return mSoundTriggerService.loadGenericSoundModel(
    251                             (GenericSoundModel) soundModel);
    252                 case SoundModel.TYPE_KEYPHRASE:
    253                     return mSoundTriggerService.loadKeyphraseSoundModel(
    254                             (KeyphraseSoundModel) soundModel);
    255                 default:
    256                     Slog.e(TAG, "Unkown model type");
    257                     return STATUS_ERROR;
    258             }
    259         } catch (RemoteException e) {
    260             throw e.rethrowFromSystemServer();
    261         }
    262     }
    263 
    264     /**
    265      * Starts recognition on the given model id. All events from the model will be sent to the
    266      * PendingIntent.
    267      * @hide
    268      */
    269     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    270     public int startRecognition(UUID soundModelId, PendingIntent callbackIntent,
    271             RecognitionConfig config) {
    272         if (soundModelId == null || callbackIntent == null || config == null) {
    273             return STATUS_ERROR;
    274         }
    275         try {
    276             return mSoundTriggerService.startRecognitionForIntent(new ParcelUuid(soundModelId),
    277                     callbackIntent, config);
    278         } catch (RemoteException e) {
    279             throw e.rethrowFromSystemServer();
    280         }
    281     }
    282 
    283     /**
    284      * Starts recognition for the given model id. All events from the model will be sent to the
    285      * service.
    286      *
    287      * <p>This only supports generic sound trigger events. For keyphrase events, please use
    288      * {@link android.service.voice.VoiceInteractionService}.
    289      *
    290      * @param soundModelId Id of the sound model
    291      * @param params Opaque data sent to each service call of the service as the {@code params}
    292      *               argument
    293      * @param detectionService The component name of the service that should receive the events.
    294      *                         Needs to subclass {@link SoundTriggerDetectionService}
    295      * @param config Configures the recognition
    296      *
    297      * @return {@link SoundTrigger#STATUS_OK} if the recognition could be started, error code
    298      *         otherwise
    299      *
    300      * @hide
    301      */
    302     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    303     public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params,
    304         @NonNull ComponentName detectionService, @NonNull RecognitionConfig config) {
    305         Preconditions.checkNotNull(soundModelId);
    306         Preconditions.checkNotNull(detectionService);
    307         Preconditions.checkNotNull(config);
    308 
    309         try {
    310             return mSoundTriggerService.startRecognitionForService(new ParcelUuid(soundModelId),
    311                 params, detectionService, config);
    312         } catch (RemoteException e) {
    313             throw e.rethrowFromSystemServer();
    314         }
    315     }
    316 
    317     /**
    318      * Stops the given model's recognition.
    319      * @hide
    320      */
    321     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    322     public int stopRecognition(UUID soundModelId) {
    323         if (soundModelId == null) {
    324             return STATUS_ERROR;
    325         }
    326         try {
    327             return mSoundTriggerService.stopRecognitionForIntent(new ParcelUuid(soundModelId));
    328         } catch (RemoteException e) {
    329             throw e.rethrowFromSystemServer();
    330         }
    331     }
    332 
    333     /**
    334      * Removes the given model from memory. Will also stop any pending recognitions.
    335      * @hide
    336      */
    337     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    338     public int unloadSoundModel(UUID soundModelId) {
    339         if (soundModelId == null) {
    340             return STATUS_ERROR;
    341         }
    342         try {
    343             return mSoundTriggerService.unloadSoundModel(
    344                     new ParcelUuid(soundModelId));
    345         } catch (RemoteException e) {
    346             throw e.rethrowFromSystemServer();
    347         }
    348     }
    349 
    350     /**
    351      * Returns true if the given model has had detection started on it.
    352      * @hide
    353      */
    354     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
    355     public boolean isRecognitionActive(UUID soundModelId) {
    356         if (soundModelId == null) {
    357             return false;
    358         }
    359         try {
    360             return mSoundTriggerService.isRecognitionActive(
    361                     new ParcelUuid(soundModelId));
    362         } catch (RemoteException e) {
    363             throw e.rethrowFromSystemServer();
    364         }
    365     }
    366 
    367     /**
    368      * Get the amount of time (in milliseconds) an operation of the
    369      * {@link ISoundTriggerDetectionService} is allowed to ask.
    370      *
    371      * @return The amount of time an sound trigger detection service operation is allowed to last
    372      */
    373     public int getDetectionServiceOperationsTimeout() {
    374         try {
    375             return Settings.Global.getInt(mContext.getContentResolver(),
    376                     Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT);
    377         } catch (Settings.SettingNotFoundException e) {
    378             return Integer.MAX_VALUE;
    379         }
    380     }
    381 }
    382