SoundTriggerHelper.java revision 78ab7cf301fd102fbfd99c69fd442ed93563a6f1
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
17package com.android.server.soundtrigger;
18
19import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
20
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.hardware.soundtrigger.IRecognitionStatusCallback;
26import android.hardware.soundtrigger.SoundTrigger;
27import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent;
28import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
29import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
30import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
31import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
32import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
33import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
34import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
35import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
36import android.hardware.soundtrigger.SoundTrigger.SoundModel;
37import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
38import android.hardware.soundtrigger.SoundTriggerModule;
39import android.os.PowerManager;
40import android.os.RemoteException;
41import android.telephony.PhoneStateListener;
42import android.telephony.TelephonyManager;
43import android.util.Slog;
44import com.android.internal.logging.MetricsLogger;
45
46import java.io.FileDescriptor;
47import java.io.PrintWriter;
48import java.util.ArrayList;
49import java.util.HashMap;
50import java.util.UUID;
51
52/**
53 * Helper for {@link SoundTrigger} APIs. Supports two types of models:
54 * (i) A voice model which is exported via the {@link VoiceInteractionService}. There can only be
55 * a single voice model running on the DSP at any given time.
56 *
57 * (ii) Generic sound-trigger models: Supports multiple of these.
58 *
59 * Currently this just acts as an abstraction over all SoundTrigger API calls.
60 * @hide
61 */
62public class SoundTriggerHelper implements SoundTrigger.StatusListener {
63    static final String TAG = "SoundTriggerHelper";
64    static final boolean DBG = false;
65
66    /**
67     * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
68     *      IRecognitionStatusCallback, RecognitionConfig)},
69     * {@link #stopRecognition(int, IRecognitionStatusCallback)}
70     */
71    public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
72    public static final int STATUS_OK = SoundTrigger.STATUS_OK;
73
74    private static final int INVALID_VALUE = Integer.MIN_VALUE;
75
76    /** The {@link ModuleProperties} for the system, or null if none exists. */
77    final ModuleProperties mModuleProperties;
78
79    /** The properties for the DSP module */
80    private SoundTriggerModule mModule;
81    private final Object mLock = new Object();
82    private final Context mContext;
83    private final TelephonyManager mTelephonyManager;
84    private final PhoneStateListener mPhoneStateListener;
85    private final PowerManager mPowerManager;
86
87    // The SoundTriggerManager layer handles multiple generic recognition models. We store the
88    // ModelData here in a hashmap.
89    private final HashMap<UUID, ModelData> mGenericModelDataMap;
90
91    // This ModelData instance ensures that the keyphrase sound model is a singleton and
92    // all other sound models are of type Generic. Any keyphrase sound model will be stored here
93    // and any previously running instances will be replaced. This restriction was earlier
94    // implemented by three instance variables which stored data about the keyphrase
95    // model. That data now gets encapsulated in this ModelData instance.
96    private ModelData mKeyphraseModelData;
97
98    // The keyphrase ID for keyphrase sound models. We store this specially here since ModelData
99    // does not support this.
100    // TODO: The role of the keyphrase ID is a bit unclear. Its just used to ensure that
101    // recognition events have the correct keyphrase ID check.
102    private int mKeyphraseId = INVALID_VALUE;
103
104    private boolean mCallActive = false;
105    private boolean mIsPowerSaveMode = false;
106    // Indicates if the native sound trigger service is disabled or not.
107    // This is an indirect indication of the microphone being open in some other application.
108    private boolean mServiceDisabled = false;
109
110    // Whether we have ANY recognition (keyphrase or generic) running.
111    private boolean mRecognitionRunning = false;
112
113    private boolean mRecognitionAborted = false;
114    private PowerSaveModeListener mPowerSaveModeListener;
115
116    SoundTriggerHelper(Context context) {
117        ArrayList <ModuleProperties> modules = new ArrayList<>();
118        int status = SoundTrigger.listModules(modules);
119        mContext = context;
120        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
121        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
122        mGenericModelDataMap = new HashMap<UUID, ModelData>();
123        mPhoneStateListener = new MyCallStateListener();
124        if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
125            Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
126            mModuleProperties = null;
127            mModule = null;
128        } else {
129            // TODO: Figure out how to determine which module corresponds to the DSP hardware.
130            mModuleProperties = modules.get(0);
131        }
132    }
133
134    /**
135     * Starts recognition for the given generic sound model ID. This is a wrapper around {@link
136     * startRecognition()}.
137     *
138     * @param modelId UUID of the sound model.
139     * @param soundModel The generic sound model to use for recognition.
140     * @param callback Callack for the recognition events related to the given keyphrase.
141     * @param recognitionConfig Instance of RecognitionConfig containing the parameters for the
142     * recognition.
143     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
144     */
145    int startGenericRecognition(UUID modelId, GenericSoundModel soundModel,
146            IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
147        MetricsLogger.count(mContext, "sth_start_recognition", 1);
148        if (modelId == null || soundModel == null || callback == null ||
149                recognitionConfig == null) {
150            Slog.w(TAG, "Passed in bad data to startGenericRecognition().");
151            return STATUS_ERROR;
152        }
153
154        synchronized (mLock) {
155            ModelData modelData = getOrCreateGenericModelDataLocked(modelId);
156            return startRecognition(soundModel, modelData, callback, recognitionConfig,
157                    INVALID_VALUE /* keyphraseId */);
158        }
159    }
160
161    /**
162     * Starts recognition for the given keyphraseId.
163     *
164     * @param keyphraseId The identifier of the keyphrase for which
165     *        the recognition is to be started.
166     * @param soundModel The sound model to use for recognition.
167     * @param callback The callback for the recognition events related to the given keyphrase.
168     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
169     */
170    int startKeyphraseRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
171            IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
172        synchronized (mLock) {
173            MetricsLogger.count(mContext, "sth_start_recognition", 1);
174            if (soundModel == null || callback == null || recognitionConfig == null) {
175                return STATUS_ERROR;
176            }
177
178            if (DBG) {
179                Slog.d(TAG, "startKeyphraseRecognition for keyphraseId=" + keyphraseId
180                        + " soundModel=" + soundModel + ", callback=" + callback.asBinder()
181                        + ", recognitionConfig=" + recognitionConfig);
182                Slog.d(TAG, "moduleProperties=" + mModuleProperties);
183                if (mKeyphraseModelData != null) {
184                    Slog.d(TAG, mKeyphraseModelData.toString());
185                } else {
186                    Slog.d(TAG, "Null KeyphraseModelData.");
187                }
188            }
189            if (mKeyphraseModelData == null) {
190                mKeyphraseModelData = ModelData.createKeyphraseModelData(soundModel.uuid);
191            }
192            return startRecognition(soundModel, mKeyphraseModelData, callback, recognitionConfig,
193                    keyphraseId);
194        }
195    }
196
197    /**
198     * Starts recognition for the given sound model. A single routine for both keyphrase and
199     * generic sound models.
200     *
201     * @param soundModel The sound model to use for recognition.
202     * @param modelData Instance of {@link #ModelData} for the given model.
203     * @param callback Callback for the recognition events related to the given keyphrase.
204     * @param recognitionConfig Instance of {@link RecognitionConfig} containing the parameters
205     * @param keyphraseId Keyphrase ID for keyphrase models only. Pass in INVALID_VALUE for other
206     * models.
207     * for the recognition.
208     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
209     */
210    int startRecognition(SoundModel soundModel, ModelData modelData,
211            IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
212            int keyphraseId) {
213        synchronized (mLock) {
214            if (mModuleProperties == null) {
215                Slog.w(TAG, "Attempting startRecognition without the capability");
216                return STATUS_ERROR;
217            }
218            if (mModule == null) {
219                mModule = SoundTrigger.attachModule(mModuleProperties.id, this, null);
220                if (mModule == null) {
221                    Slog.w(TAG, "startRecognition cannot attach to sound trigger module");
222                    return STATUS_ERROR;
223                }
224            }
225
226            // Initialize power save, call active state monitoring logic.
227            if (!mRecognitionRunning) {
228                initializeTelephonyAndPowerStateListeners();
229            }
230
231            // If the previous model is different (for the same UUID), ensure that its unloaded
232            // and stopped before proceeding. This works for both keyphrase and generic models.
233            // Specifically for keyphrase since we have 'mKeyphraseModelData' holding a single
234            // allowed instance of such a model, this ensures that a previously loaded (or started)
235            // keyphrase model is appropriately stopped. This ensures no regression with the
236            // previous version of this code as given in the startKeyphrase() routine.
237            //
238            // For generic sound models, all this means is that if we are given a different sound
239            // model with the same UUID, then we will "replace" it.
240            if (modelData.getSoundModel() != null) {
241                boolean stopModel = false; // Stop the model after checking that its started.
242                boolean unloadModel = false;
243                if (modelData.getSoundModel().equals(soundModel) && modelData.isModelStarted()) {
244                    // The model has not changed, but the previous model is "started".
245                    // Stop the previously running model.
246                    stopModel = true;
247                    unloadModel = false; // No need to unload if the model hasn't changed.
248                } else if (!modelData.getSoundModel().equals(soundModel)) {
249                    // We have a different model for this UUID. Stop and unload if needed. This
250                    // helps maintain the singleton restriction for keyphrase sound models.
251                    stopModel = modelData.isModelStarted();
252                    unloadModel = modelData.isModelLoaded();
253                }
254                if (stopModel || unloadModel) {
255                    int status = tryStopAndUnloadLocked(modelData, stopModel, unloadModel);
256                    if (status != STATUS_OK) {
257                        Slog.w(TAG, "Unable to stop or unload previous model: " +
258                                modelData.toString());
259                        return status;
260                    }
261                }
262            }
263
264            IRecognitionStatusCallback oldCallback = modelData.getCallback();
265            if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
266                Slog.w(TAG, "Canceling previous recognition for model id: " +
267                        modelData.getModelId());
268                try {
269                    oldCallback.onError(STATUS_ERROR);
270                } catch (RemoteException e) {
271                    Slog.w(TAG, "RemoteException in onDetectionStopped", e);
272                }
273                modelData.clearCallback();
274            }
275
276            // Load the model if its not loaded.
277            if (!modelData.isModelLoaded()) {
278                // Load the model
279                int[] handle = new int[] { INVALID_VALUE };
280                int status = mModule.loadSoundModel(soundModel, handle);
281                if (status != SoundTrigger.STATUS_OK) {
282                    Slog.w(TAG, "loadSoundModel call failed with " + status);
283                    return status;
284                }
285                if (handle[0] == INVALID_VALUE) {
286                    Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
287                    return STATUS_ERROR;
288                }
289                modelData.setHandle(handle[0]);
290                modelData.setLoaded();
291                Slog.d(TAG, "Sound model loaded with handle:" + handle[0]);
292            }
293            modelData.setCallback(callback);
294            if (modelData.isKeyphraseModel()) {
295                mKeyphraseId = keyphraseId;
296            }
297            modelData.setRequested(true);
298            modelData.setRecognitionConfig(recognitionConfig);
299            modelData.setSoundModel(soundModel);
300
301            return startRecognitionLocked(modelData,
302                    false /* Don't notify for synchronous calls */);
303        }
304    }
305
306    /**
307     * Stops recognition for the given generic sound model. This is a wrapper for {@link
308     * #stopRecognition}.
309     *
310     * @param modelId The identifier of the generic sound model for which
311     *        the recognition is to be stopped.
312     * @param callback The callback for the recognition events related to the given sound model.
313     *
314     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
315     */
316    int stopGenericRecognition(UUID modelId, IRecognitionStatusCallback callback) {
317        synchronized (mLock) {
318            MetricsLogger.count(mContext, "sth_stop_recognition", 1);
319            if (callback == null || modelId == null) {
320                Slog.e(TAG, "Null callbackreceived for stopGenericRecognition() for modelid:" +
321                        modelId);
322                return STATUS_ERROR;
323            }
324
325            ModelData modelData = mGenericModelDataMap.get(modelId);
326            if (modelData == null) {
327                Slog.w(TAG, "Attempting stopRecognition on invalid model with id:" + modelId);
328                return STATUS_ERROR;
329            }
330
331            int status = stopRecognition(modelData, callback);
332            if (status != SoundTrigger.STATUS_OK) {
333                Slog.w(TAG, "stopGenericRecognition failed: " + status);
334            }
335            return status;
336        }
337    }
338
339    /**
340     * Stops recognition for the given {@link Keyphrase} if a recognition is
341     * currently active. This is a wrapper for {@link #stopRecognition()}.
342     *
343     * @param keyphraseId The identifier of the keyphrase for which
344     *        the recognition is to be stopped.
345     * @param callback The callback for the recognition events related to the given keyphrase.
346     *
347     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
348     */
349    int stopKeyphraseRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
350        synchronized (mLock) {
351            MetricsLogger.count(mContext, "sth_stop_recognition", 1);
352            if (callback == null) {
353                Slog.e(TAG, "Null callback received for stopKeyphraseRecognition() for keyphraseId:" +
354                        keyphraseId);
355                return STATUS_ERROR;
356            }
357
358            if (DBG) {
359                Slog.d(TAG, "stopRecognition for keyphraseId=" + keyphraseId + ", callback =" +
360                        callback.asBinder());
361                Slog.d(TAG, "current callback=" + (mKeyphraseModelData == null ? "null" :
362                            mKeyphraseModelData.getCallback().asBinder()));
363            }
364            int status = stopRecognition(mKeyphraseModelData, callback);
365            if (status != SoundTrigger.STATUS_OK) {
366                return status;
367            }
368
369            // We leave the sound model loaded but not started, this helps us when we start
370            // back.
371            // Also clear the internal state once the recognition has been stopped.
372            internalClearKeyphraseStateLocked();
373            return status;
374        }
375    }
376
377    /**
378     * Stops recognition for the given ModelData instance.
379     *
380     * @param modelData Instance of {@link #ModelData} sound model.
381     * @param callback The callback for the recognition events related to the given keyphrase.
382     * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
383     */
384    private int stopRecognition(ModelData modelData, IRecognitionStatusCallback callback) {
385        synchronized (mLock) {
386            if (callback == null) {
387                return STATUS_ERROR;
388            }
389            if (mModuleProperties == null || mModule == null) {
390                Slog.w(TAG, "Attempting stopRecognition without the capability");
391                return STATUS_ERROR;
392            }
393
394            IRecognitionStatusCallback currentCallback = modelData.getCallback();
395            if (modelData == null || currentCallback == null || !modelData.isModelStarted()) {
396                // startGenericRecognition hasn't been called or it failed.
397                Slog.w(TAG, "Attempting stopGenericRecognition without a successful" +
398                        " startGenericRecognition");
399                return STATUS_ERROR;
400            }
401
402            if (currentCallback.asBinder() != callback.asBinder()) {
403                // We don't allow a different listener to stop the recognition than the one
404                // that started it.
405                Slog.w(TAG, "Attempting stopRecognition for another recognition");
406                return STATUS_ERROR;
407            }
408
409            // Request stop recognition via the update() method.
410            modelData.setRequested(false);
411            int status = updateRecognitionLocked(modelData, isRecognitionAllowed(),
412                    false /* don't notify for synchronous calls */);
413            if (status != SoundTrigger.STATUS_OK) {
414                return status;
415            }
416
417            // We leave the sound model loaded but not started, this helps us when we start back.
418            // Also clear the internal state once the recognition has been stopped.
419            modelData.setLoaded();
420            modelData.clearCallback();
421            modelData.setRecognitionConfig(null);
422
423            if (!computeRecognitionRunningLocked()) {
424                internalClearGlobalStateLocked();
425            }
426
427            if (modelData.isKeyphraseModel()) {
428                mKeyphraseId = INVALID_VALUE;
429            }
430            return status;
431        }
432    }
433
434    // Stop a previously started model if it was started. Optionally, unload if the previous model
435    // is stale and is about to be replaced.
436    // Needs to be called with the mLock held.
437    private int tryStopAndUnloadLocked(ModelData modelData, boolean stopModel,
438            boolean unloadModel) {
439        int status = STATUS_OK;
440        if (modelData.isModelNotLoaded()) {
441            return status;
442        }
443        if (stopModel && modelData.isModelStarted()) {
444            status = stopRecognitionLocked(modelData,
445                    false /* don't notify for synchronous calls */);
446            if (status != SoundTrigger.STATUS_OK) {
447                Slog.w(TAG, "stopRecognition failed: " + status);
448                return status;
449            }
450        }
451
452        if (unloadModel && modelData.isModelLoaded()) {
453            Slog.d(TAG, "Unloading previously loaded stale model.");
454            status = mModule.unloadSoundModel(modelData.getHandle());
455            MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
456            if (status != SoundTrigger.STATUS_OK) {
457                Slog.w(TAG, "unloadSoundModel call failed with " + status);
458            } else {
459                // Clear the ModelData state if successful.
460                modelData.clearState();
461                modelData.clearCallback();
462                modelData.setRecognitionConfig(null);
463            }
464        }
465        return status;
466    }
467
468    /**
469     * Stops all recognitions active currently and clears the internal state.
470     */
471    void stopAllRecognitions() {
472        synchronized (mLock) {
473            MetricsLogger.count(mContext, "sth_stop_all_recognitions", 1);
474            if (mModuleProperties == null || mModule == null) {
475                return;
476            }
477
478            // Stop Keyphrase recognition if one exists.
479            if (mKeyphraseModelData != null && mKeyphraseModelData.getHandle() != INVALID_VALUE) {
480                mKeyphraseModelData.setRequested(false);
481                int status = updateRecognitionLocked(mKeyphraseModelData, isRecognitionAllowed(),
482                        false /* don't notify for synchronous calls */);
483                internalClearKeyphraseStateLocked();
484            }
485
486            // Stop all generic recognition models.
487            for (ModelData model : mGenericModelDataMap.values()) {
488                if (model.isModelStarted()) {
489                    int status = stopRecognitionLocked(model,
490                            false /* do not notify for synchronous calls */);
491                    if (status != STATUS_OK) {
492                        // What else can we do if there is an error here.
493                        Slog.w(TAG, "Error stopping generic model: " + model.getHandle());
494                    }
495                    model.clearState();
496                    model.clearCallback();
497                }
498            }
499            internalClearGlobalStateLocked();
500        }
501    }
502
503    public ModuleProperties getModuleProperties() {
504        return mModuleProperties;
505    }
506
507    int unloadKeyphraseSoundModel(int keyphraseId) {
508        synchronized (mLock) {
509            MetricsLogger.count(mContext, "sth_unload_keyphrase_sound_model", 1);
510            if (mModule == null || mKeyphraseModelData == null ||
511                    mKeyphraseModelData.getHandle() == INVALID_VALUE) {
512                return STATUS_ERROR;
513            }
514
515            // Stop recognition if it's the current one.
516            mKeyphraseModelData.setRequested(false);
517            int status = updateRecognitionLocked(mKeyphraseModelData, isRecognitionAllowed(),
518                    false /* don't notify */);
519            if (status != SoundTrigger.STATUS_OK) {
520                Slog.w(TAG, "Stop recognition failed for keyphrase ID:" + status);
521            }
522
523            status = mModule.unloadSoundModel(mKeyphraseModelData.getHandle());
524            if (status != SoundTrigger.STATUS_OK) {
525                Slog.w(TAG, "unloadKeyphraseSoundModel call failed with " + status);
526            }
527            mKeyphraseModelData.clearState();
528            return status;
529        }
530    }
531
532    int unloadGenericSoundModel(UUID modelId) {
533        synchronized (mLock) {
534            MetricsLogger.count(mContext, "sth_unload_generic_sound_model", 1);
535            if (modelId == null || mModule == null) {
536                return STATUS_ERROR;
537            }
538            ModelData modelData = mGenericModelDataMap.get(modelId);
539            if (modelData == null) {
540                Slog.w(TAG, "Unload error: Attempting unload invalid generic model with id:" +
541                        modelId);
542                return STATUS_ERROR;
543            }
544            if (!modelData.isModelLoaded()) {
545                // Nothing to do here.
546                Slog.i(TAG, "Unload: Given generic model is not loaded:" + modelId);
547                return STATUS_OK;
548            }
549            if (modelData.isModelStarted()) {
550                int status = stopRecognitionLocked(modelData,
551                        false /* don't notify for synchronous calls */);
552                if (status != SoundTrigger.STATUS_OK) {
553                    Slog.w(TAG, "stopGenericRecognition failed: " + status);
554                }
555            }
556
557            int status = mModule.unloadSoundModel(modelData.getHandle());
558            if (status != SoundTrigger.STATUS_OK) {
559                Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
560                Slog.w(TAG, "unloadGenericSoundModel() force-marking model as unloaded.");
561            }
562            mGenericModelDataMap.remove(modelId);
563            if (DBG) dumpGenericModelStateLocked();
564            return status;
565        }
566    }
567
568    //---- SoundTrigger.StatusListener methods
569    @Override
570    public void onRecognition(RecognitionEvent event) {
571        if (event == null) {
572            Slog.w(TAG, "Null recognition event!");
573            return;
574        }
575
576        if (!(event instanceof KeyphraseRecognitionEvent) &&
577                !(event instanceof GenericRecognitionEvent)) {
578            Slog.w(TAG, "Invalid recognition event type (not one of generic or keyphrase) !");
579            return;
580        }
581
582        if (DBG) Slog.d(TAG, "onRecognition: " + event);
583        synchronized (mLock) {
584            switch (event.status) {
585                // Fire aborts/failures to all listeners since it's not tied to a keyphrase.
586                case SoundTrigger.RECOGNITION_STATUS_ABORT:
587                    onRecognitionAbortLocked();
588                    break;
589                case SoundTrigger.RECOGNITION_STATUS_FAILURE:
590                    onRecognitionFailureLocked();
591                    break;
592                case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
593
594                    if (isKeyphraseRecognitionEvent(event)) {
595                        onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
596                    } else {
597                        onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
598                    }
599
600                    break;
601            }
602        }
603    }
604
605    private boolean isKeyphraseRecognitionEvent(RecognitionEvent event) {
606        return event instanceof KeyphraseRecognitionEvent;
607    }
608
609    private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
610        MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
611        if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS) {
612            return;
613        }
614        ModelData model = getModelDataForLocked(event.soundModelHandle);
615        if (model == null) {
616            Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " +
617                    event.soundModelHandle);
618            return;
619        }
620
621        IRecognitionStatusCallback callback = model.getCallback();
622        if (callback == null) {
623            Slog.w(TAG, "Generic recognition event: Null callback for model handle: " +
624                    event.soundModelHandle);
625            return;
626        }
627
628        try {
629            callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
630        } catch (RemoteException e) {
631            Slog.w(TAG, "RemoteException in onGenericSoundTriggerDetected", e);
632        }
633
634        model.setStopped();
635        RecognitionConfig config = model.getRecognitionConfig();
636        if (config == null) {
637            Slog.w(TAG, "Generic recognition event: Null RecognitionConfig for model handle: " +
638                    event.soundModelHandle);
639            return;
640        }
641
642        model.setRequested(config.allowMultipleTriggers);
643        // TODO: Remove this block if the lower layer supports multiple triggers.
644        if (model.getRequested()) {
645            updateRecognitionLocked(model, isRecognitionAllowed() /* isAllowed */,
646                    true /* notify */);
647        }
648    }
649
650    @Override
651    public void onSoundModelUpdate(SoundModelEvent event) {
652        if (event == null) {
653            Slog.w(TAG, "Invalid sound model event!");
654            return;
655        }
656        if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
657        synchronized (mLock) {
658            MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
659            onSoundModelUpdatedLocked(event);
660        }
661    }
662
663    @Override
664    public void onServiceStateChange(int state) {
665        if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
666        synchronized (mLock) {
667            onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
668        }
669    }
670
671    @Override
672    public void onServiceDied() {
673        Slog.e(TAG, "onServiceDied!!");
674        MetricsLogger.count(mContext, "sth_service_died", 1);
675        synchronized (mLock) {
676            onServiceDiedLocked();
677        }
678    }
679
680    private void onCallStateChangedLocked(boolean callActive) {
681        if (mCallActive == callActive) {
682            // We consider multiple call states as being active
683            // so we check if something really changed or not here.
684            return;
685        }
686        mCallActive = callActive;
687        updateAllRecognitionsLocked(true /* notify */);
688    }
689
690    private void onPowerSaveModeChangedLocked(boolean isPowerSaveMode) {
691        if (mIsPowerSaveMode == isPowerSaveMode) {
692            return;
693        }
694        mIsPowerSaveMode = isPowerSaveMode;
695        updateAllRecognitionsLocked(true /* notify */);
696    }
697
698    private void onSoundModelUpdatedLocked(SoundModelEvent event) {
699        // TODO: Handle sound model update here.
700    }
701
702    private void onServiceStateChangedLocked(boolean disabled) {
703        if (disabled == mServiceDisabled) {
704            return;
705        }
706        mServiceDisabled = disabled;
707        updateAllRecognitionsLocked(true /* notify */);
708    }
709
710    private void onRecognitionAbortLocked() {
711        Slog.w(TAG, "Recognition aborted");
712        MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
713        // If abort has been called, the hardware has already stopped recognition, so we shouldn't
714        // call it again when we process the state change.
715        mRecognitionAborted = true;
716    }
717
718    private void onRecognitionFailureLocked() {
719        Slog.w(TAG, "Recognition failure");
720        MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
721        try {
722            sendErrorCallbacksToAll(STATUS_ERROR);
723        } catch (RemoteException e) {
724            Slog.w(TAG, "RemoteException in onError", e);
725        } finally {
726            internalClearKeyphraseStateLocked();
727            internalClearGenericModelStateLocked();
728            internalClearGlobalStateLocked();
729        }
730    }
731
732    private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
733        Slog.i(TAG, "Recognition success");
734        MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
735
736        if (mKeyphraseModelData == null) {
737            Slog.e(TAG, "Received onRecognition event for null keyphrase model data.");
738            return;
739        }
740
741        if (mKeyphraseModelData.getCallback() == null) {
742            Slog.w(TAG, "Received onRecognition event without any listener for it.");
743            return;
744        }
745
746        KeyphraseRecognitionExtra[] keyphraseExtras =
747                ((KeyphraseRecognitionEvent) event).keyphraseExtras;
748        if (keyphraseExtras == null || keyphraseExtras.length == 0) {
749            Slog.w(TAG, "Invalid keyphrase recognition event!");
750            return;
751        }
752        // TODO: Handle more than one keyphrase extras.
753        if (mKeyphraseId != keyphraseExtras[0].id) {
754            Slog.w(TAG, "received onRecognition event for a different keyphrase");
755            return;
756        }
757
758        try {
759            mKeyphraseModelData.getCallback().onKeyphraseDetected(
760                    (KeyphraseRecognitionEvent) event);
761        } catch (RemoteException e) {
762            Slog.w(TAG, "RemoteException in onKeyphraseDetected", e);
763        }
764
765        mKeyphraseModelData.setStopped();
766
767        RecognitionConfig config = mKeyphraseModelData.getRecognitionConfig();
768        if (config != null) {
769            // Whether we should continue by starting this again.
770            mKeyphraseModelData.setRequested(config.allowMultipleTriggers);
771        }
772        // TODO: Remove this block if the lower layer supports multiple triggers.
773        if (mKeyphraseModelData.getRequested()) {
774            updateRecognitionLocked(mKeyphraseModelData, isRecognitionAllowed(),
775                true /* notify */);
776        }
777    }
778
779    private void updateAllRecognitionsLocked(boolean notify) {
780        boolean isAllowed = isRecognitionAllowed();
781        // Keyphrase model.
782        if (mKeyphraseModelData != null) {
783            updateRecognitionLocked(mKeyphraseModelData, isAllowed, notify);
784        }
785        for (UUID modelId : mGenericModelDataMap.keySet()) {
786            ModelData modelData = mGenericModelDataMap.get(modelId);
787            updateRecognitionLocked(modelData, isAllowed, notify);
788        }
789    }
790
791    private int updateRecognitionLocked(ModelData model, boolean isAllowed,
792        boolean notify) {
793        boolean start = model.getRequested() && isAllowed;
794        if (start == model.isModelStarted()) {
795            // No-op.
796            return STATUS_OK;
797        }
798        if (start) {
799            return startRecognitionLocked(model, notify);
800        } else {
801            return stopRecognitionLocked(model, notify);
802        }
803    }
804
805    private void onServiceDiedLocked() {
806        try {
807          MetricsLogger.count(mContext, "sth_service_died", 1);
808            sendErrorCallbacksToAll(SoundTrigger.STATUS_DEAD_OBJECT);
809        } catch (RemoteException e) {
810            Slog.w(TAG, "RemoteException in onError", e);
811        } finally {
812            if (mKeyphraseModelData != null) {
813                mKeyphraseModelData.clearState();
814            }
815            internalClearKeyphraseStateLocked();
816            internalClearGenericModelStateLocked();
817            internalClearGlobalStateLocked();
818            if (mModule != null) {
819                mModule.detach();
820                mModule = null;
821            }
822        }
823    }
824
825    // internalClearGlobalStateLocked() gets split into two routines. Cleanup that is
826    // specific to keyphrase sound models named as internalClearKeyphraseStateLocked() and
827    // internalClearGlobalStateLocked() for global state. The global cleanup routine will be used
828    // by the cleanup happening with the generic sound models.
829    private void internalClearGlobalStateLocked() {
830        // Unregister from call state changes.
831        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
832
833        // Unregister from power save mode changes.
834        if (mPowerSaveModeListener != null) {
835            mContext.unregisterReceiver(mPowerSaveModeListener);
836            mPowerSaveModeListener = null;
837        }
838    }
839
840    private void internalClearKeyphraseStateLocked() {
841        if (mKeyphraseModelData != null) {
842            mKeyphraseModelData.setStopped();
843            mKeyphraseModelData.setRequested(false);
844            mKeyphraseModelData.setRecognitionConfig(null);
845            mKeyphraseModelData.setCallback(null);
846        }
847
848        mKeyphraseId = INVALID_VALUE;
849    }
850
851    private void internalClearGenericModelStateLocked() {
852        for (UUID modelId : mGenericModelDataMap.keySet()) {
853            ModelData modelData = mGenericModelDataMap.get(modelId);
854            modelData.clearState();
855            modelData.clearCallback();
856        }
857    }
858
859    class MyCallStateListener extends PhoneStateListener {
860        @Override
861        public void onCallStateChanged(int state, String arg1) {
862            if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
863            synchronized (mLock) {
864                onCallStateChangedLocked(TelephonyManager.CALL_STATE_IDLE != state);
865            }
866        }
867    }
868
869    class PowerSaveModeListener extends BroadcastReceiver {
870        @Override
871        public void onReceive(Context context, Intent intent) {
872            if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
873                return;
874            }
875            boolean active = mPowerManager.isPowerSaveMode();
876            if (DBG) Slog.d(TAG, "onPowerSaveModeChanged: " + active);
877            synchronized (mLock) {
878                onPowerSaveModeChangedLocked(active);
879            }
880        }
881    }
882
883    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
884        synchronized (mLock) {
885            pw.print("  module properties=");
886            pw.println(mModuleProperties == null ? "null" : mModuleProperties);
887            pw.print("  keyphrase ID="); pw.println(mKeyphraseId);
888
889            pw.print("  call active="); pw.println(mCallActive);
890            pw.print("  power save mode active="); pw.println(mIsPowerSaveMode);
891            pw.print("  service disabled="); pw.println(mServiceDisabled);
892            if (mKeyphraseModelData != null) {
893                pw.println(mKeyphraseModelData.toString());
894            }
895        }
896    }
897
898    private void initializeTelephonyAndPowerStateListeners() {
899        // Get the current call state synchronously for the first recognition.
900        mCallActive = mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
901
902        // Register for call state changes when the first call to start recognition occurs.
903        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
904
905        // Register for power saver mode changes when the first call to start recognition
906        // occurs.
907        if (mPowerSaveModeListener == null) {
908            mPowerSaveModeListener = new PowerSaveModeListener();
909            mContext.registerReceiver(mPowerSaveModeListener,
910                    new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
911        }
912        mIsPowerSaveMode = mPowerManager.isPowerSaveMode();
913    }
914
915    // Sends an error callback to all models with a valid registered callback.
916    private void sendErrorCallbacksToAll(int errorCode) throws RemoteException {
917        IRecognitionStatusCallback keyphraseListener = mKeyphraseModelData.getCallback();
918        if (keyphraseListener != null) {
919            keyphraseListener.onError(STATUS_ERROR);
920        }
921        for (UUID modelId: mGenericModelDataMap.keySet()) {
922            ModelData modelData = mGenericModelDataMap.get(modelId);
923            IRecognitionStatusCallback keyphraseCallback = mKeyphraseModelData.getCallback();
924            if (keyphraseCallback != null) {
925                keyphraseCallback.onError(STATUS_ERROR);
926            }
927        }
928    }
929
930    private ModelData getOrCreateGenericModelDataLocked(UUID modelId) {
931        ModelData modelData = mGenericModelDataMap.get(modelId);
932        if (modelData == null) {
933            modelData = ModelData.createGenericModelData(modelId);
934            mGenericModelDataMap.put(modelId, modelData);
935        }
936        return modelData;
937    }
938
939    // Instead of maintaining a second hashmap of modelHandle -> ModelData, we just
940    // iterate through to find the right object (since we don't expect 100s of models
941    // to be stored).
942    private ModelData getModelDataForLocked(int modelHandle) {
943        // Fetch ModelData object corresponding to the model handle.
944        for (ModelData model : mGenericModelDataMap.values()) {
945            if (model.getHandle() == modelHandle) {
946                return model;
947            }
948        }
949        return null;
950    }
951
952    // Whether we are allowed to run any recognition at all. The conditions that let us run
953    // a recognition include: no active phone call or not being in a power save mode. Also,
954    // the native service should be enabled.
955    private boolean isRecognitionAllowed() {
956        return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
957    }
958
959    // A single routine that implements the start recognition logic for both generic and keyphrase
960    // models.
961    private int startRecognitionLocked(ModelData modelData, boolean notify) {
962        IRecognitionStatusCallback callback = modelData.getCallback();
963        int handle = modelData.getHandle();
964        RecognitionConfig config = modelData.getRecognitionConfig();
965        if (callback == null || handle == INVALID_VALUE || config == null) {
966            // Nothing to do here.
967            Slog.w(TAG, "startRecognition: Bad data passed in.");
968            MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
969            return STATUS_ERROR;
970        }
971
972        if (!isRecognitionAllowed()) {
973            // Nothing to do here.
974            Slog.w(TAG, "startRecognition requested but not allowed.");
975            MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
976            return STATUS_OK;
977        }
978
979        int status = mModule.startRecognition(handle, config);
980        if (status != SoundTrigger.STATUS_OK) {
981            Slog.w(TAG, "startRecognition failed with " + status);
982            MetricsLogger.count(mContext, "sth_start_recognition_error", 1);
983            // Notify of error if needed.
984            if (notify) {
985                try {
986                    callback.onError(status);
987                } catch (RemoteException e) {
988                    Slog.w(TAG, "RemoteException in onError", e);
989                }
990            }
991        } else {
992            Slog.i(TAG, "startRecognition successful.");
993            MetricsLogger.count(mContext, "sth_start_recognition_success", 1);
994            modelData.setStarted();
995            // Notify of resume if needed.
996            if (notify) {
997                try {
998                    callback.onRecognitionResumed();
999                } catch (RemoteException e) {
1000                    Slog.w(TAG, "RemoteException in onRecognitionResumed", e);
1001                }
1002            }
1003        }
1004        if (DBG) {
1005            Slog.d(TAG, "Model being started :" + modelData.toString());
1006        }
1007        return status;
1008    }
1009
1010    private int stopRecognitionLocked(ModelData modelData, boolean notify) {
1011        IRecognitionStatusCallback callback = modelData.getCallback();
1012
1013        // Stop recognition (only if we haven't been aborted).
1014        int status = STATUS_OK;
1015
1016        // This logic for "recognition aborted" now works for both generic and keyphrase models.
1017        // The idea here is to "skip" the stopRecognition() call if the lower layer has
1018        // aborted recognition. Also we "consume" the abort state as well, so if there is another
1019        // stopRecognition() request, it will go through -- this seems to have been the previously
1020        // intended design.
1021        if (!mRecognitionAborted) {
1022            status = mModule.stopRecognition(modelData.getHandle());
1023        } else {
1024            mRecognitionAborted = false;
1025        }
1026        if (status != SoundTrigger.STATUS_OK) {
1027            Slog.w(TAG, "stopRecognition call failed with " + status);
1028            MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
1029            if (notify) {
1030                try {
1031                    callback.onError(status);
1032                } catch (RemoteException e) {
1033                    Slog.w(TAG, "RemoteException in onError", e);
1034                }
1035            }
1036        } else {
1037            modelData.setStopped();
1038            MetricsLogger.count(mContext, "sth_stop_recognition_success", 1);
1039            // Notify of pause if needed.
1040            if (notify) {
1041                try {
1042                    callback.onRecognitionPaused();
1043                } catch (RemoteException e) {
1044                    Slog.w(TAG, "RemoteException in onRecognitionPaused", e);
1045                }
1046            }
1047        }
1048        if (DBG) {
1049            Slog.d(TAG, "Model being stopped :" + modelData.toString());
1050        }
1051        return status;
1052    }
1053
1054    private void dumpGenericModelStateLocked() {
1055        for (UUID modelId : mGenericModelDataMap.keySet()) {
1056            ModelData modelData = mGenericModelDataMap.get(modelId);
1057            Slog.i(TAG, "Model :" + modelData.toString());
1058        }
1059    }
1060
1061    // Computes whether we have any recognition running at all (voice or generic). Sets
1062    // the mRecognitionRunning variable with the result.
1063    private boolean computeRecognitionRunningLocked() {
1064        if (mModuleProperties == null || mModule == null) {
1065            mRecognitionRunning = false;
1066            return mRecognitionRunning;
1067        }
1068        if (mKeyphraseModelData != null && mKeyphraseModelData.getCallback() != null &&
1069                mKeyphraseModelData.isModelStarted() &&
1070            mKeyphraseModelData.getHandle() != INVALID_VALUE) {
1071            mRecognitionRunning = true;
1072            return mRecognitionRunning;
1073        }
1074        for (UUID modelId : mGenericModelDataMap.keySet()) {
1075            ModelData modelData = mGenericModelDataMap.get(modelId);
1076            if (modelData.isModelStarted()) {
1077                mRecognitionRunning = true;
1078                return mRecognitionRunning;
1079            }
1080        }
1081        mRecognitionRunning = false;
1082        return mRecognitionRunning;
1083    }
1084
1085    // This class encapsulates the callbacks, state, handles and any other information that
1086    // represents a model.
1087    private static class ModelData {
1088        // Model not loaded (and hence not started).
1089        static final int MODEL_NOTLOADED = 0;
1090
1091        // Loaded implies model was successfully loaded. Model not started yet.
1092        static final int MODEL_LOADED = 1;
1093
1094        // Started implies model was successfully loaded and start was called.
1095        static final int MODEL_STARTED = 2;
1096
1097        // One of MODEL_NOTLOADED, MODEL_LOADED, MODEL_STARTED (which implies loaded).
1098        private int mModelState;
1099        private UUID mModelId;
1100
1101        // mRequested captures the explicit intent that a start was requested for this model. We
1102        // continue to capture and retain this state even after the model gets started, so that we
1103        // know when a model gets stopped due to "other" reasons, that we should start it again.
1104        // This was the intended behavior of the "mRequested" variable in the previous version of
1105        // this code that we are replicating here.
1106        //
1107        // The "other" reasons include power save, abort being called from the lower layer (due
1108        // to concurrent capture not being supported) and phone call state. Once we recover from
1109        // these transient disruptions, we would start such models again where mRequested == true.
1110        // Thus, mRequested gets reset only when there is an explicit intent to stop the model
1111        // coming from the SoundTriggerService layer that uses this class (and thus eventually
1112        // from the app that manages this model).
1113        private boolean mRequested = false;
1114
1115        // One of SoundModel.TYPE_GENERIC or SoundModel.TYPE_KEYPHRASE. Initially set
1116        // to SoundModel.TYPE_UNKNOWN;
1117        private int mModelType = SoundModel.TYPE_UNKNOWN;
1118
1119        private IRecognitionStatusCallback mCallback = null;
1120        private RecognitionConfig mRecognitionConfig = null;
1121
1122        // Model handle is an integer used by the HAL as an identifier for sound
1123        // models.
1124        private int mModelHandle = INVALID_VALUE;
1125
1126        // The SoundModel instance, one of KeyphraseSoundModel or GenericSoundModel.
1127        private SoundModel mSoundModel = null;
1128
1129        private ModelData(UUID modelId, int modelType) {
1130            mModelId = modelId;
1131            // Private constructor, since we require modelType to be one of TYPE_GENERIC,
1132            // TYPE_KEYPHRASE or TYPE_UNKNOWN.
1133            mModelType = modelType;
1134        }
1135
1136        static ModelData createKeyphraseModelData(UUID modelId) {
1137            return new ModelData(modelId, SoundModel.TYPE_KEYPHRASE);
1138        }
1139
1140        static ModelData createGenericModelData(UUID modelId) {
1141            return new ModelData(modelId, SoundModel.TYPE_GENERIC_SOUND);
1142        }
1143
1144        // Note that most of the functionality in this Java class will not work for
1145        // SoundModel.TYPE_UNKNOWN nevertheless we have it since lower layers support it.
1146        static ModelData createModelDataOfUnknownType(UUID modelId) {
1147            return new ModelData(modelId, SoundModel.TYPE_UNKNOWN);
1148        }
1149
1150        synchronized void setCallback(IRecognitionStatusCallback callback) {
1151            mCallback = callback;
1152        }
1153
1154        synchronized IRecognitionStatusCallback getCallback() {
1155            return mCallback;
1156        }
1157
1158        synchronized boolean isModelLoaded() {
1159            return (mModelState == MODEL_LOADED || mModelState == MODEL_STARTED);
1160        }
1161
1162        synchronized boolean isModelNotLoaded() {
1163            return mModelState == MODEL_NOTLOADED;
1164        }
1165
1166        synchronized void setStarted() {
1167            mModelState = MODEL_STARTED;
1168        }
1169
1170        synchronized void setStopped() {
1171            mModelState = MODEL_LOADED;
1172        }
1173
1174        synchronized void setLoaded() {
1175            mModelState = MODEL_LOADED;
1176        }
1177
1178        synchronized boolean isModelStarted() {
1179            return mModelState == MODEL_STARTED;
1180        }
1181
1182        synchronized void clearState() {
1183            mModelState = MODEL_NOTLOADED;
1184            mModelHandle = INVALID_VALUE;
1185        }
1186
1187        synchronized void clearCallback() {
1188            mCallback = null;
1189        }
1190
1191        synchronized void setHandle(int handle) {
1192            mModelHandle = handle;
1193        }
1194
1195        synchronized void setRecognitionConfig(RecognitionConfig config) {
1196            mRecognitionConfig = config;
1197        }
1198
1199        synchronized int getHandle() {
1200            return mModelHandle;
1201        }
1202
1203        synchronized UUID getModelId() {
1204            return mModelId;
1205        }
1206
1207        synchronized RecognitionConfig getRecognitionConfig() {
1208            return mRecognitionConfig;
1209        }
1210
1211        // Whether a start recognition was requested.
1212        synchronized boolean getRequested() {
1213            return mRequested;
1214        }
1215
1216        synchronized void setRequested(boolean requested) {
1217            mRequested = requested;
1218        }
1219
1220        synchronized void setSoundModel(SoundModel soundModel) {
1221            mSoundModel = soundModel;
1222        }
1223
1224        synchronized SoundModel getSoundModel() {
1225            return mSoundModel;
1226        }
1227
1228        synchronized int getModelType() {
1229            return mModelType;
1230        }
1231
1232        synchronized boolean isKeyphraseModel() {
1233            return mModelType == SoundModel.TYPE_KEYPHRASE;
1234        }
1235
1236        synchronized String stateToString() {
1237            switch(mModelState) {
1238                case MODEL_NOTLOADED: return "NOT_LOADED";
1239                case MODEL_LOADED: return "LOADED";
1240                case MODEL_STARTED: return "STARTED";
1241            }
1242            return "Unknown state";
1243        }
1244
1245        synchronized String requestedToString() {
1246            return "Requested: " + (mRequested ? "Yes" : "No");
1247        }
1248
1249        synchronized String callbackToString() {
1250            return "Callback: " + (mCallback != null ? mCallback.asBinder() : "null");
1251        }
1252
1253        synchronized String uuidToString() {
1254            return "UUID: " + mModelId;
1255        }
1256
1257        synchronized public String toString() {
1258            return "Handle: " + mModelHandle + "\n" +
1259                    "ModelState: " + stateToString() + "\n" +
1260                    requestedToString() + "\n" +
1261                    callbackToString() + "\n" +
1262                    uuidToString();
1263        }
1264    }
1265}
1266