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

Commit 828eef12 authored by Aleksandar Kiridzic's avatar Aleksandar Kiridzic Committed by Aleksandar Kiridžić
Browse files

speech: API council feedback from model download with a listener

A single listener corresponds to a single model download request
identified by the intent. Hence, instead of setting and clearing
the listener explicitly, the whole pipeline will be covered by
a triggerModelDownload overload.

Bug: 271016482
Test: CTS
Change-Id: I580359009316a325c77e432f74faa7e2431b813a
parent f7e5e147
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -41555,7 +41555,6 @@ package android.speech {
  public abstract class RecognitionService extends android.app.Service {
    ctor public RecognitionService();
    method public void clearModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
    method public int getMaxConcurrentSessionsCount();
    method public final android.os.IBinder onBind(android.content.Intent);
    method protected abstract void onCancel(android.speech.RecognitionService.Callback);
@@ -41565,7 +41564,7 @@ package android.speech {
    method protected abstract void onStopListening(android.speech.RecognitionService.Callback);
    method public void onTriggerModelDownload(@NonNull android.content.Intent);
    method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource);
    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
    method public void onTriggerModelDownload(@NonNull android.content.Intent, @NonNull android.content.AttributionSource, @NonNull android.speech.ModelDownloadListener);
    field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
    field public static final String SERVICE_META_DATA = "android.speech";
  }
@@ -41690,18 +41689,17 @@ package android.speech {
  public class SpeechRecognizer {
    method @MainThread public void cancel();
    method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.RecognitionSupportCallback);
    method public void clearModelDownloadListener(@NonNull android.content.Intent);
    method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context);
    method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context);
    method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName);
    method public void destroy();
    method public static boolean isOnDeviceRecognitionAvailable(@NonNull android.content.Context);
    method public static boolean isRecognitionAvailable(@NonNull android.content.Context);
    method public void setModelDownloadListener(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
    method @MainThread public void setRecognitionListener(android.speech.RecognitionListener);
    method @MainThread public void startListening(android.content.Intent);
    method @MainThread public void stopListening();
    method public void triggerModelDownload(@NonNull android.content.Intent);
    method public void triggerModelDownload(@NonNull android.content.Intent, @NonNull java.util.concurrent.Executor, @NonNull android.speech.ModelDownloadListener);
    field public static final String CONFIDENCE_SCORES = "confidence_scores";
    field public static final String DETECTED_LANGUAGE = "detected_language";
    field public static final int ERROR_AUDIO = 3; // 0x3
+2 −15
Original line number Diff line number Diff line
@@ -78,23 +78,10 @@ oneway interface IRecognitionService {
     * information see {@link #checkRecognitionSupport},  {@link #startListening} and
     * {@link RecognizerIntent}.
     *
     * Progress can be monitord by calling {@link #setModelDownloadListener} before a trigger.
     * Progress updates can be received via {@link #IModelDownloadListener}.
     */
    void triggerModelDownload(in Intent recognizerIntent, in AttributionSource attributionSource);

    /**
     * Sets listener to received download progress updates. Clients still have to call
     * {@link #triggerModelDownload} to trigger a model download.
     */
    void setModelDownloadListener(
    void triggerModelDownload(
        in Intent recognizerIntent,
        in AttributionSource attributionSource,
        in IModelDownloadListener listener);

    /**
     * Clears the listener for model download events attached to a recognitionIntent if any.
     */
    void clearModelDownloadListener(
        in Intent recognizerIntent,
        in AttributionSource attributionSource);
}
+12 −5
Original line number Diff line number Diff line
@@ -22,20 +22,27 @@ package android.speech;
 */
public interface ModelDownloadListener {
    /**
     * Called by {@link RecognitionService} when there's an update on the download progress.
     * Called by {@link RecognitionService} only if the download has started after the request.
     *
     * <p>RecognitionService will call this zero or more times during the download.</p>
     * <p> The number of calls to this method varies depending of the {@link RecognitionService}
     * implementation. If the download finished quickly enough, {@link #onSuccess()} may be called
     * directly. In other cases, this method may be called any number of times during the download.
     *
     * @param completedPercent the percentage of download that is completed
     */
    void onProgress(int completedPercent);

    /**
     * Called when {@link RecognitionService} completed the download and it can now be used to
     * satisfy recognition requests.
     * This method is called:
     * <li> if the model is already available;
     * <li> if the {@link RecognitionService} has started and completed the download.
     *
     * <p> Once this method is called, the model can be safely used to satisfy recognition requests.
     */
    void onSuccess();

    /**
     * Called when {@link RecognitionService} scheduled the download but won't satisfy it
     * Called when {@link RecognitionService} scheduled the download, but won't satisfy it
     * immediately. There will be no further updates on this listener.
     */
    void onScheduled();
+87 −131
Original line number Diff line number Diff line
@@ -36,9 +36,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.android.internal.util.function.pooled.PooledLambda;

@@ -93,10 +91,6 @@ public abstract class RecognitionService extends Service {

    private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6;

    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 7;

    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 8;

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
@@ -120,21 +114,11 @@ public abstract class RecognitionService extends Service {
                            checkArgs.mIntent, checkArgs.callback, checkArgs.mAttributionSource);
                    break;
                case MSG_TRIGGER_MODEL_DOWNLOAD:
                    Pair<Intent, AttributionSource> params =
                            (Pair<Intent, AttributionSource>) msg.obj;
                    dispatchTriggerModelDownload(params.first, params.second);
                    break;
                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
                    ModelDownloadListenerArgs dListenerArgs = (ModelDownloadListenerArgs) msg.obj;
                    dispatchSetModelDownloadListener(
                            dListenerArgs.mIntent,
                            dListenerArgs.mListener,
                            dListenerArgs.mAttributionSource);
                    break;
                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
                    Pair<Intent, AttributionSource> clearDlPair =
                            (Pair<Intent, AttributionSource>) msg.obj;
                    dispatchClearModelDownloadListener(clearDlPair.first, clearDlPair.second);
                    ModelDownloadArgs modelDownloadArgs = (ModelDownloadArgs) msg.obj;
                    dispatchTriggerModelDownload(
                            modelDownloadArgs.mIntent,
                            modelDownloadArgs.mAttributionSource,
                            modelDownloadArgs.mListener);
                    break;
            }
        }
@@ -239,15 +223,12 @@ public abstract class RecognitionService extends Service {

    private void dispatchTriggerModelDownload(
            Intent intent,
            AttributionSource attributionSource) {
            AttributionSource attributionSource,
            IModelDownloadListener listener) {
        if (listener == null) {
            RecognitionService.this.onTriggerModelDownload(intent, attributionSource);
    }

    private void dispatchSetModelDownloadListener(
            Intent intent,
            IModelDownloadListener listener,
            AttributionSource attributionSource) {
        RecognitionService.this.setModelDownloadListener(
        } else {
            RecognitionService.this.onTriggerModelDownload(
                    intent,
                    attributionSource,
                    new ModelDownloadListener() {
@@ -288,10 +269,6 @@ public abstract class RecognitionService extends Service {
                        }
                    });
        }

    private void dispatchClearModelDownloadListener(
            Intent intent, AttributionSource attributionSource) {
        RecognitionService.this.clearModelDownloadListener(intent, attributionSource);
    }

    private static class StartListeningArgs {
@@ -323,17 +300,18 @@ public abstract class RecognitionService extends Service {
        }
    }

    private static class ModelDownloadListenerArgs {
    private static class ModelDownloadArgs {
        final Intent mIntent;
        final IModelDownloadListener mListener;
        final AttributionSource mAttributionSource;
        @Nullable final IModelDownloadListener mListener;

        private ModelDownloadListenerArgs(Intent intent,
                IModelDownloadListener listener,
                AttributionSource attributionSource) {
            mIntent = intent;
        private ModelDownloadArgs(
                Intent intent,
                AttributionSource attributionSource,
                @Nullable IModelDownloadListener listener) {
            this.mIntent = intent;
            this.mAttributionSource = attributionSource;
            this.mListener = listener;
            mAttributionSource = attributionSource;
        }
    }

@@ -443,38 +421,39 @@ public abstract class RecognitionService extends Service {
    }

    /**
     * Sets a {@link ModelDownloadListener} to receive progress updates after
     * {@link #onTriggerModelDownload} calls.
     * Requests the download of the recognizer support for {@code recognizerIntent}.
     *
     * @param recognizerIntent the request to monitor model download progress for.
     * @param modelDownloadListener the listener to keep updated.
     */
    public void setModelDownloadListener(
            @NonNull Intent recognizerIntent,
            @NonNull AttributionSource attributionSource,
            @NonNull ModelDownloadListener modelDownloadListener) {
        if (DBG) {
            Log.i(TAG, TextUtils.formatSimple(
                    "#setModelDownloadListener [%s] [%s]",
                    recognizerIntent,
                    modelDownloadListener));
        }
        modelDownloadListener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
    }

    /**
     * Clears the {@link ModelDownloadListener} set to receive progress updates for the given
     * {@code recognizerIntent}, if any.
     * <p> Provides the calling {@link AttributionSource} to the service implementation so that
     * permissions and bandwidth could be correctly blamed.
     *
     * <p> Client will receive the progress updates via the given {@link ModelDownloadListener}:
     *
     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
     * called directly. The model can be safely used afterwards.
     *
     * @param recognizerIntent the request to monitor model download progress for.
     * <li> If the {@link RecognitionService} has started the download,
     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
     * number of times until the download is complete.
     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
     * The model can be safely used afterwards.
     *
     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
     * There will be no further updates on this listener.
     *
     * <li> If the request fails at any time due to a network or scheduling error,
     * {@link ModelDownloadListener#onError(int)} will be called.
     *
     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
     *        may also contain optional extras, see {@link RecognizerIntent}.
     * @param attributionSource the attribution source of the caller.
     * @param listener on which to receive updates about the model download request.
     */
    public void clearModelDownloadListener(
    public void onTriggerModelDownload(
            @NonNull Intent recognizerIntent,
            @NonNull AttributionSource attributionSource) {
        if (DBG) {
            Log.i(TAG, TextUtils.formatSimple(
                    "#clearModelDownloadListener [%s]", recognizerIntent));
        }
            @NonNull AttributionSource attributionSource,
            @NonNull ModelDownloadListener listener) {
        listener.onError(SpeechRecognizer.ERROR_CANNOT_LISTEN_TO_DOWNLOAD_EVENTS);
    }

    @Override
@@ -815,41 +794,18 @@ public abstract class RecognitionService extends Service {

        @Override
        public void triggerModelDownload(
                Intent recognizerIntent, @NonNull AttributionSource attributionSource) {
                Intent recognizerIntent,
                @NonNull AttributionSource attributionSource,
                IModelDownloadListener listener) {
            final RecognitionService service = mServiceRef.get();
            if (service != null) {
                service.mHandler.sendMessage(
                        Message.obtain(
                                service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
                                Pair.create(recognizerIntent, attributionSource)));
            }
        }

        @Override
        public void setModelDownloadListener(
                Intent recognizerIntent,
                AttributionSource attributionSource,
                IModelDownloadListener listener) throws RemoteException {
            final RecognitionService service = mServiceRef.get();
            if (service != null) {
                service.mHandler.sendMessage(
                        Message.obtain(service.mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
                                new ModelDownloadListenerArgs(
                                new ModelDownloadArgs(
                                        recognizerIntent,
                                        listener,
                                        attributionSource)));
            }
        }

        @Override
        public void clearModelDownloadListener(
                Intent recognizerIntent,
                AttributionSource attributionSource) throws RemoteException {
            final RecognitionService service = mServiceRef.get();
            if (service != null) {
                service.mHandler.sendMessage(
                        Message.obtain(service.mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER,
                                Pair.create(recognizerIntent, attributionSource)));
                                        attributionSource,
                                        listener)));
            }
        }

+56 −80
Original line number Diff line number Diff line
@@ -297,8 +297,6 @@ public class SpeechRecognizer {
    private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
    private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
    private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
    private static final int MSG_SET_MODEL_DOWNLOAD_LISTENER = 8;
    private static final int MSG_CLEAR_MODEL_DOWNLOAD_LISTENER = 9;

    /** The actual RecognitionService endpoint */
    private IRecognitionService mService;
@@ -341,19 +339,13 @@ public class SpeechRecognizer {
                            args.mIntent, args.mCallbackExecutor, args.mCallback);
                    break;
                case MSG_TRIGGER_MODEL_DOWNLOAD:
                    handleTriggerModelDownload((Intent) msg.obj);
                    break;
                case MSG_SET_MODEL_DOWNLOAD_LISTENER:
                    ModelDownloadListenerArgs modelDownloadListenerArgs =
                            (ModelDownloadListenerArgs) msg.obj;
                    handleSetModelDownloadListener(
                    handleTriggerModelDownload(
                            modelDownloadListenerArgs.mIntent,
                            modelDownloadListenerArgs.mExecutor,
                            modelDownloadListenerArgs.mModelDownloadListener);
                    break;
                case MSG_CLEAR_MODEL_DOWNLOAD_LISTENER:
                    handleClearModelDownloadListener((Intent) msg.obj);
                    break;
            }
        }
    };
@@ -657,17 +649,13 @@ public class SpeechRecognizer {
     * user interaction to approve the download. Callers can verify the status of the request via
     * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
     *
     * <p>Listeners set via
     * {@link #setModelDownloadListener(Intent, Executor, ModelDownloadListener)} will receive
     * updates about this download request.</p>
     *
     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
     *        may also contain optional extras, see {@link RecognizerIntent}.
     */
    public void triggerModelDownload(@NonNull Intent recognizerIntent) {
        Objects.requireNonNull(recognizerIntent, "intent must not be null");
        if (DBG) {
            Slog.i(TAG, "#triggerModelDownload called");
            Slog.i(TAG, "#triggerModelDownload without a listener called");
            if (mService == null) {
                Slog.i(TAG, "Connection is not established yet");
            }
@@ -676,23 +664,47 @@ public class SpeechRecognizer {
            // First time connection: first establish a connection, then dispatch.
            connectToSystemService();
        }
        putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent));
        putMessage(Message.obtain(
                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
                new ModelDownloadListenerArgs(recognizerIntent, null, null)));
    }

    /**
     * Sets a listener to model download updates. Clients will have to call this method before
     * {@link #triggerModelDownload(Intent)}.
     * Attempts to download the support for the given {@code recognizerIntent}. This might trigger
     * user interaction to approve the download. Callers can verify the status of the request via
     * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}.
     *
     * <p> The updates about the model download request are received via the given
     * {@link ModelDownloadListener}:
     *
     * <li> If the model is already available, {@link ModelDownloadListener#onSuccess()} will be
     * called directly. The model can be safely used afterwards.
     *
     * <li> If the {@link RecognitionService} has started the download,
     * {@link ModelDownloadListener#onProgress(int)} will be called an unspecified (zero or more)
     * number of times until the download is complete.
     * When the download finishes, {@link ModelDownloadListener#onSuccess()} will be called.
     * The model can be safely used afterwards.
     *
     * <li> If the {@link RecognitionService} has only scheduled the download, but won't satisfy it
     * immediately, {@link ModelDownloadListener#onScheduled()} will be called.
     * There will be no further updates on this listener.
     *
     * <li> If the request fails at any time due to a network or scheduling error,
     * {@link ModelDownloadListener#onError(int)} will be called.
     *
     * @param recognizerIntent the request to monitor support for.
     * @param recognizerIntent contains parameters for the recognition to be performed. The intent
     *        may also contain optional extras, see {@link RecognizerIntent}.
     * @param executor for dispatching listener callbacks
     * @param listener on which to receive updates about the model download request.
     */
    public void setModelDownloadListener(
    public void triggerModelDownload(
            @NonNull Intent recognizerIntent,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull ModelDownloadListener listener) {
        Objects.requireNonNull(recognizerIntent, "intent must not be null");
        if (DBG) {
            Slog.i(TAG, "#setModelDownloadListener called");
            Slog.i(TAG, "#triggerModelDownload with a listener called");
            if (mService == null) {
                Slog.i(TAG, "Connection is not established yet");
            }
@@ -702,31 +714,10 @@ public class SpeechRecognizer {
            connectToSystemService();
        }
        putMessage(Message.obtain(
                mHandler, MSG_SET_MODEL_DOWNLOAD_LISTENER,
                mHandler, MSG_TRIGGER_MODEL_DOWNLOAD,
                new ModelDownloadListenerArgs(recognizerIntent, executor, listener)));
    }

    /**
     * Clears the listener for model download updates if any.
     *
     * @param recognizerIntent the request to monitor support for.
     */
    public void clearModelDownloadListener(@NonNull Intent recognizerIntent) {
        Objects.requireNonNull(recognizerIntent, "intent must not be null");
        if (DBG) {
            Slog.i(TAG, "#clearModelDownloadListener called");
            if (mService == null) {
                Slog.i(TAG, "Connection is not established yet");
            }
        }
        if (mService == null) {
            // First time connection: first establish a connection, then dispatch.
            connectToSystemService();
        }
        putMessage(Message.obtain(
                mHandler, MSG_CLEAR_MODEL_DOWNLOAD_LISTENER, recognizerIntent));
    }

    /**
     * Sets a temporary component to power on-device speech recognizer.
     *
@@ -836,52 +827,37 @@ public class SpeechRecognizer {
        }
    }

    private void handleTriggerModelDownload(Intent recognizerIntent) {
    private void handleTriggerModelDownload(
            Intent recognizerIntent,
            @Nullable Executor callbackExecutor,
            @Nullable ModelDownloadListener modelDownloadListener) {
        if (!maybeInitializeManagerService()) {
            return;
        }

        // Trigger model download without a listener.
        if (modelDownloadListener == null) {
            try {
            mService.triggerModelDownload(recognizerIntent, mContext.getAttributionSource());
                mService.triggerModelDownload(
                        recognizerIntent, mContext.getAttributionSource(), null);
                if (DBG) Log.d(TAG, "triggerModelDownload() without a listener");
            } catch (final RemoteException e) {
            Log.e(TAG, "downloadModel() failed", e);
                Log.e(TAG, "triggerModelDownload() without a listener failed", e);
                mListener.onError(ERROR_CLIENT);
            }
        }

    private void handleSetModelDownloadListener(
            Intent recognizerIntent,
            Executor callbackExecutor,
            @Nullable ModelDownloadListener modelDownloadListener) {
        if (!maybeInitializeManagerService()) {
            return;
        }
        // Trigger model download with a listener.
        else {
            try {
            InternalModelDownloadListener listener =
                    modelDownloadListener == null
                            ? null
                            : new InternalModelDownloadListener(
                                    callbackExecutor,
                                    modelDownloadListener);
            mService.setModelDownloadListener(
                    recognizerIntent, mContext.getAttributionSource(), listener);
            if (DBG) Log.d(TAG, "setModelDownloadListener()");
                mService.triggerModelDownload(
                        recognizerIntent, mContext.getAttributionSource(),
                        new InternalModelDownloadListener(callbackExecutor, modelDownloadListener));
                if (DBG) Log.d(TAG, "triggerModelDownload() with a listener");
            } catch (final RemoteException e) {
            Log.e(TAG, "setModelDownloadListener() failed", e);
                Log.e(TAG, "triggerModelDownload() with a listener failed", e);
                callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT));
            }
        }

    private void handleClearModelDownloadListener(Intent recognizerIntent) {
        if (!maybeInitializeManagerService()) {
            return;
        }
        try {
            mService.clearModelDownloadListener(
                    recognizerIntent, mContext.getAttributionSource());
            if (DBG) Log.d(TAG, "clearModelDownloadListener()");
        } catch (final RemoteException e) {
            Log.e(TAG, "clearModelDownloadListener() failed", e);
        }
    }

    private boolean checkOpenConnection() {
Loading