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

Commit 2fa9e782 authored by Peter Li's avatar Peter Li Committed by Android (Google) Code Review
Browse files

Merge "Implement timeout mechanism when HotwordDetectionService initializes" into sc-dev

parents 676a66ef 9c9f5aa6
Loading
Loading
Loading
Loading
+9 −6
Original line number Diff line number Diff line
@@ -28,8 +28,10 @@ import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.media.AudioFormat;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -37,8 +39,6 @@ import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.Log;

import com.android.internal.app.IHotwordRecognitionStatusCallback;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -58,6 +58,8 @@ public abstract class HotwordDetectionService extends Service {
    private static final boolean DBG = true;

    private static final long UPDATE_TIMEOUT_MILLIS = 5000;
    /** @hide */
    public static final String KEY_INITIALIZATION_STATUS = "initialization_status";

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
@@ -141,7 +143,7 @@ public abstract class HotwordDetectionService extends Service {

        @Override
        public void updateState(PersistableBundle options, SharedMemory sharedMemory,
                IHotwordRecognitionStatusCallback callback) throws RemoteException {
                IRemoteCallback callback) throws RemoteException {
            if (DBG) {
                Log.d(TAG, "#updateState");
            }
@@ -314,14 +316,15 @@ public abstract class HotwordDetectionService extends Service {
    }

    private void onUpdateStateInternal(@Nullable PersistableBundle options,
            @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
        // TODO (b/183684347): Implement timeout case.
            @Nullable SharedMemory sharedMemory, IRemoteCallback callback) {
        IntConsumer intConsumer = null;
        if (callback != null) {
            intConsumer =
                    value -> {
                        try {
                            callback.onStatusReported(value);
                            Bundle status = new Bundle();
                            status.putInt(KEY_INITIALIZATION_STATUS, value);
                            callback.sendResult(status);
                        } catch (RemoteException e) {
                            throw e.rethrowFromSystemServer();
                        }
+2 −3
Original line number Diff line number Diff line
@@ -17,13 +17,12 @@
package android.service.voice;

import android.media.AudioFormat;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.service.voice.IDspHotwordDetectionCallback;

import com.android.internal.app.IHotwordRecognitionStatusCallback;

/**
 * Provide the interface to communicate with hotword detection service.
 *
@@ -44,5 +43,5 @@ oneway interface IHotwordDetectionService {
        in IDspHotwordDetectionCallback callback);

    void updateState(in PersistableBundle options, in SharedMemory sharedMemory,
            in IHotwordRecognitionStatusCallback callback);
            in IRemoteCallback callback);
}
+73 −8
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.server.voiceinteraction;

import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,6 +33,8 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -46,6 +50,7 @@ import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;

import java.io.Closeable;
@@ -58,6 +63,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A class that provides the communication with the HotwordDetectionService.
@@ -75,11 +82,13 @@ final class HotwordDetectionConnection {
    private static final int MAX_STREAMING_SECONDS = 10;
    private static final int MICROPHONE_BUFFER_LENGTH_SECONDS = 8;
    private static final int HOTWORD_AUDIO_LENGTH_SECONDS = 3;
    private static final long MAX_UPDATE_TIMEOUT_MILLIS = 6000;

    private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
    // TODO: This may need to be a Handler(looper)
    private final ScheduledExecutorService mScheduledExecutorService =
            Executors.newSingleThreadScheduledExecutor();
    private final AtomicBoolean mUpdateStateFinish = new AtomicBoolean(false);

    final Object mLock;
    final ComponentName mDetectionComponentName;
@@ -107,16 +116,11 @@ final class HotwordDetectionConnection {
            @Override // from ServiceConnector.Impl
            protected void onServiceConnectionStatusChanged(IHotwordDetectionService service,
                    boolean connected) {
                if (DEBUG) {
                    Slog.d(TAG, "onServiceConnectionStatusChanged connected = " + connected);
                }
                synchronized (mLock) {
                    mBound = connected;
                    if (connected) {
                        try {
                            service.updateState(options, sharedMemory, callback);
                        } catch (RemoteException e) {
                            // TODO: (b/181842909) Report an error to voice interactor
                            Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
                        }
                    }
                }
            }

@@ -126,6 +130,67 @@ final class HotwordDetectionConnection {
            }
        };
        mRemoteHotwordDetectionService.connect();
        if (callback == null) {
            updateStateLocked(options, sharedMemory);
            return;
        }
        updateStateWithCallbackLocked(options, sharedMemory, callback);
    }

    private void updateStateWithCallbackLocked(PersistableBundle options,
            SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
        if (DEBUG) {
            Slog.d(TAG, "updateStateWithCallbackLocked");
        }
        mRemoteHotwordDetectionService.postAsync(service -> {
            AndroidFuture<Void> future = new AndroidFuture<>();
            IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
                @Override
                public void sendResult(Bundle bundle) throws RemoteException {
                    if (DEBUG) {
                        Slog.d(TAG, "updateState finish");
                    }
                    future.complete(null);
                    try {
                        if (mUpdateStateFinish.getAndSet(true)) {
                            Slog.w(TAG, "call callback after timeout");
                            return;
                        }
                        int status = bundle != null ? bundle.getInt(
                                KEY_INITIALIZATION_STATUS,
                                INITIALIZATION_STATUS_UNKNOWN)
                                : INITIALIZATION_STATUS_UNKNOWN;
                        callback.onStatusReported(status);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "Failed to report initialization status: " + e);
                    }
                }
            };
            try {
                service.updateState(options, sharedMemory, statusCallback);
            } catch (RemoteException e) {
                // TODO: (b/181842909) Report an error to voice interactor
                Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
            }
            return future;
        }).orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                .whenComplete((res, err) -> {
                    if (err instanceof TimeoutException) {
                        Slog.w(TAG, "updateState timed out");
                        try {
                            if (mUpdateStateFinish.getAndSet(true)) {
                                return;
                            }
                            callback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
                        } catch (RemoteException e) {
                            Slog.w(TAG, "Failed to report initialization status: " + e);
                        }
                    } else if (err != null) {
                        Slog.w(TAG, "Failed to update state: " + err);
                    } else {
                        // NOTE: so far we don't need to take any action.
                    }
                });
    }

    private boolean isBound() {