Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 72695ffa authored by Ytai Ben-Tsvi's avatar Ytai Ben-Tsvi
Browse files

Support preemptive model unloading

This change plumbs soundtrigger preemptive model unloading, typically
initiated by the STHAL, all the way to SoundTriggerService, where
logic for retry / lazy loading already exists.

Test: Manual verification of existing soundtrigger use-cases.
Change-Id: I49195fbf2ccd6268e7fbc0279ec6fe04c1cc016e
parent 2cdd7037
Loading
Loading
Loading
Loading
+15 −123
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.hardware.soundtrigger;
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY;
import static android.system.OsConstants.EBUSY;
import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENODEV;
import static android.system.OsConstants.ENOSYS;
@@ -91,6 +92,8 @@ public class SoundTrigger {
    public static final int STATUS_DEAD_OBJECT = -EPIPE;
    /** @hide */
    public static final int STATUS_INVALID_OPERATION = -ENOSYS;
    /** @hide */
    public static final int STATUS_BUSY = -EBUSY;

    /*****************************************************************************
     * A ModuleProperties describes a given sound trigger hardware module
@@ -1835,120 +1838,6 @@ public class SoundTrigger {
        }
    }

    /**
     *  Status codes for {@link SoundModelEvent}
     */
    /**
     * Sound Model was updated
     *
     * @hide
     */
    public static final int SOUNDMODEL_STATUS_UPDATED = 0;

    /**
     *  A SoundModelEvent is provided by the
     *  {@link StatusListener#onSoundModelUpdate(SoundModelEvent)}
     *  callback when a sound model has been updated by the implementation
     *
     *  @hide
     */
    public static class SoundModelEvent implements Parcelable {
        /** Status e.g {@link #SOUNDMODEL_STATUS_UPDATED} */
        public final int status;
        /** The updated sound model handle */
        public final int soundModelHandle;
        /** New sound model data */
        @NonNull
        public final byte[] data;

        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        SoundModelEvent(int status, int soundModelHandle, @Nullable byte[] data) {
            this.status = status;
            this.soundModelHandle = soundModelHandle;
            this.data = data != null ? data : new byte[0];
        }

        public static final @android.annotation.NonNull Parcelable.Creator<SoundModelEvent> CREATOR
                = new Parcelable.Creator<SoundModelEvent>() {
            public SoundModelEvent createFromParcel(Parcel in) {
                return SoundModelEvent.fromParcel(in);
            }

            public SoundModelEvent[] newArray(int size) {
                return new SoundModelEvent[size];
            }
        };

        private static SoundModelEvent fromParcel(Parcel in) {
            int status = in.readInt();
            int soundModelHandle = in.readInt();
            byte[] data = in.readBlob();
            return new SoundModelEvent(status, soundModelHandle, data);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(status);
            dest.writeInt(soundModelHandle);
            dest.writeBlob(data);
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + Arrays.hashCode(data);
            result = prime * result + soundModelHandle;
            result = prime * result + status;
            return result;
        }

        @Override
        public boolean equals(@Nullable Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SoundModelEvent other = (SoundModelEvent) obj;
            if (!Arrays.equals(data, other.data))
                return false;
            if (soundModelHandle != other.soundModelHandle)
                return false;
            if (status != other.status)
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "SoundModelEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
                    + ", data=" + (data == null ? 0 : data.length) + "]";
        }
    }

    /**
     *  Native service state. {@link StatusListener#onServiceStateChange(int)}
     */
    // Keep in sync with system/core/include/system/sound_trigger.h
    /**
     * Sound trigger service is enabled
     *
     * @hide
     */
    public static final int SERVICE_STATE_ENABLED = 0;
    /**
     * Sound trigger service is disabled
     *
     * @hide
     */
    public static final int SERVICE_STATE_DISABLED = 1;
    private static Object mServiceLock = new Object();
    private static ISoundTriggerMiddlewareService mService;

@@ -1975,6 +1864,8 @@ public class SoundTrigger {
                    return STATUS_DEAD_OBJECT;
                case Status.INTERNAL_ERROR:
                    return STATUS_ERROR;
                case Status.RESOURCE_CONTENTION:
                    return STATUS_BUSY;
            }
            return STATUS_ERROR;
        }
@@ -2224,27 +2115,28 @@ public class SoundTrigger {
     *
     * @hide
     */
    public static interface StatusListener {
    public interface StatusListener {
        /**
         * Called when recognition succeeds of fails
         */
        public abstract void onRecognition(RecognitionEvent event);
        void onRecognition(RecognitionEvent event);

        /**
         * Called when a sound model has been updated
         * Called when a sound model has been preemptively unloaded by the underlying
         * implementation.
         */
        public abstract void onSoundModelUpdate(SoundModelEvent event);
        void onModelUnloaded(int modelHandle);

        /**
         * Called when the sound trigger native service state changes.
         * @param state Native service state. One of {@link SoundTrigger#SERVICE_STATE_ENABLED},
         * {@link SoundTrigger#SERVICE_STATE_DISABLED}
         * Called whenever underlying conditions change, such that load/start operations that have
         * previously failed or got preempted may now succeed. This is not a guarantee, merely a
         * hint that the client may want to retry operations.
         */
        public abstract void onServiceStateChange(int state);
        void onResourceConditionChange();

        /**
         * Called when the sound trigger native service dies
         */
        public abstract void onServiceDied();
        void onServiceDied();
    }
}
+12 −11
Original line number Diff line number Diff line
@@ -48,7 +48,8 @@ public class SoundTriggerModule {

    private static final int EVENT_RECOGNITION = 1;
    private static final int EVENT_SERVICE_DIED = 2;
    private static final int EVENT_SERVICE_STATE_CHANGE = 3;
    private static final int EVENT_RESOURCE_CONDITION_CHANGE = 3;
    private static final int EVENT_MODEL_UNLOADED = 4;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private int mId;
    private EventHandlerDelegate mEventHandlerDelegate;
@@ -120,6 +121,7 @@ public class SoundTriggerModule {
     * @param soundModelHandle an array of int where the sound model handle will be returned.
     * @return - {@link SoundTrigger#STATUS_OK} in case of success
     *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
     *         - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints
     *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
     *         system permission
     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
@@ -181,6 +183,7 @@ public class SoundTriggerModule {
     *  recognition mode, keyphrases, users, minimum confidence levels...
     * @return - {@link SoundTrigger#STATUS_OK} in case of success
     *         - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
     *         - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints
     *         - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
     *         system permission
     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
@@ -333,8 +336,11 @@ public class SoundTriggerModule {
                            listener.onRecognition(
                                    (SoundTrigger.RecognitionEvent) msg.obj);
                            break;
                        case EVENT_SERVICE_STATE_CHANGE:
                            listener.onServiceStateChange((int) msg.obj);
                        case EVENT_RESOURCE_CONDITION_CHANGE:
                            listener.onResourceConditionChange();
                            break;
                        case EVENT_MODEL_UNLOADED:
                            listener.onModelUnloaded((Integer) msg.obj);
                            break;
                        case EVENT_SERVICE_DIED:
                            listener.onServiceDied();
@@ -365,18 +371,13 @@ public class SoundTriggerModule {

        @Override
        public void onModelUnloaded(int modelHandle) throws RemoteException {
            // TODO: Implement
            throw new RuntimeException("Implement me");
            Message m = mHandler.obtainMessage(EVENT_MODEL_UNLOADED, modelHandle);
            mHandler.sendMessage(m);
        }

        @Override
        public synchronized void onResourceConditionChange() throws RemoteException {
            // TODO: Temporary hack.
            Message m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
                    SoundTrigger.SERVICE_STATE_DISABLED);
            mHandler.sendMessage(m);
            m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
                    SoundTrigger.SERVICE_STATE_ENABLED);
            Message m = mHandler.obtainMessage(EVENT_RESOURCE_CONDITION_CHANGE);
            mHandler.sendMessage(m);
        }

+17 −26
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
import android.hardware.soundtrigger.SoundTriggerModule;
import android.os.Binder;
import android.os.DeadObjectException;
@@ -109,9 +108,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
    private boolean mCallActive = false;
    private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode =
            PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
    // Indicates if the native sound trigger service is disabled or not.
    // This is an indirect indication of the microphone being open in some other application.
    private boolean mServiceDisabled = false;

    // Whether ANY recognition (keyphrase or generic) has been requested.
    private boolean mRecognitionRequested = false;
@@ -862,23 +858,19 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
    }

    @Override
    public void onSoundModelUpdate(SoundModelEvent event) {
        if (event == null) {
            Slog.w(TAG, "Invalid sound model event!");
            return;
        }
        if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
    public void onModelUnloaded(int modelHandle) {
        if (DBG) Slog.d(TAG, "onModelUnloaded: " + modelHandle);
        synchronized (mLock) {
            MetricsLogger.count(mContext, "sth_sound_model_updated", 1);
            onSoundModelUpdatedLocked(event);
            onModelUnloadedLocked(modelHandle);
        }
    }

    @Override
    public void onServiceStateChange(int state) {
        if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
    public void onResourceConditionChange() {
        if (DBG) Slog.d(TAG, "onResourceConditionChange");
        synchronized (mLock) {
            onServiceStateChangedLocked(SoundTrigger.SERVICE_STATE_DISABLED == state);
            onResourceConditionChangeLocked();
        }
    }

@@ -910,15 +902,14 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
        updateAllRecognitionsLocked();
    }

    private void onSoundModelUpdatedLocked(SoundModelEvent event) {
        // TODO: Handle sound model update here.
    private void onModelUnloadedLocked(int modelHandle) {
        ModelData modelData = getModelDataForLocked(modelHandle);
        if (modelData != null) {
            modelData.setNotLoaded();
        }

    private void onServiceStateChangedLocked(boolean disabled) {
        if (disabled == mServiceDisabled) {
            return;
    }
        mServiceDisabled = disabled;

    private void onResourceConditionChangeLocked() {
        updateAllRecognitionsLocked();
    }

@@ -1039,7 +1030,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
            if (mModule != null) {
                mModule.detach();
                mModule = null;
                mServiceDisabled = false;
            }
        }
    }
@@ -1114,8 +1104,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
            pw.print("  call active=");
            pw.println(mCallActive);
            pw.println("  SoundTrigger Power State=" + mSoundTriggerPowerSaveMode);
            pw.print("  service disabled=");
            pw.println(mServiceDisabled);
        }
    }

@@ -1329,8 +1317,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
            mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
        }

        return !mCallActive && !mServiceDisabled
                && isRecognitionAllowedByPowerState(
        return !mCallActive && isRecognitionAllowedByPowerState(
                modelData);
    }

@@ -1571,6 +1558,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
            mModelState = MODEL_LOADED;
        }

        synchronized void setNotLoaded() {
            mModelState = MODEL_NOTLOADED;
        }

        synchronized boolean isModelStarted() {
            return mModelState == MODEL_STARTED;
        }