diff --git a/core/api/current.txt b/core/api/current.txt index f38c237df25d6..02f2efab17648 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -39859,8 +39859,10 @@ package android.speech { ctor public RecognitionService(); method public final android.os.IBinder onBind(android.content.Intent); method protected abstract void onCancel(android.speech.RecognitionService.Callback); + method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback); method protected abstract void onStartListening(android.content.Intent, android.speech.RecognitionService.Callback); method protected abstract void onStopListening(android.speech.RecognitionService.Callback); + method public void triggerModelDownload(@NonNull android.content.Intent); field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService"; field public static final String SERVICE_META_DATA = "android.speech"; } @@ -39878,6 +39880,36 @@ package android.speech { method public void rmsChanged(float) throws android.os.RemoteException; } + public static class RecognitionService.SupportCallback { + method public void onError(int); + method public void onSupportResult(@NonNull android.speech.RecognitionSupport); + } + + public final class RecognitionSupport implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List getInstalledLanguages(); + method @NonNull public java.util.List getPendingLanguages(); + method @NonNull public java.util.List getSupportedLanguages(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class RecognitionSupport.Builder { + ctor public RecognitionSupport.Builder(); + method @NonNull public android.speech.RecognitionSupport.Builder addInstalledLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addPendingLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addSupportedLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport build(); + method @NonNull public android.speech.RecognitionSupport.Builder setInstalledLanguages(@NonNull java.util.List); + method @NonNull public android.speech.RecognitionSupport.Builder setPendingLanguages(@NonNull java.util.List); + method @NonNull public android.speech.RecognitionSupport.Builder setSupportedLanguages(@NonNull java.util.List); + } + + public interface RecognitionSupportCallback { + method public void onError(int); + method public void onSupportResult(@NonNull android.speech.RecognitionSupport); + } + public class RecognizerIntent { method public static final android.content.Intent getVoiceDetailsIntent(android.content.Context); field public static final String ACTION_GET_LANGUAGE_DETAILS = "android.speech.action.GET_LANGUAGE_DETAILS"; @@ -39927,6 +39959,7 @@ package android.speech { public class SpeechRecognizer { method @MainThread public void cancel(); + method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionSupportCallback); 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); @@ -39936,8 +39969,10 @@ package android.speech { 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); field public static final String CONFIDENCE_SCORES = "confidence_scores"; field public static final int ERROR_AUDIO = 3; // 0x3 + field public static final int ERROR_CANNOT_CHECK_SUPPORT = 14; // 0xe field public static final int ERROR_CLIENT = 5; // 0x5 field public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9; // 0x9 field public static final int ERROR_LANGUAGE_NOT_SUPPORTED = 12; // 0xc diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl index cc349c8d030d9..ad3ad7ad1e053 100644 --- a/core/java/android/speech/IRecognitionService.aidl +++ b/core/java/android/speech/IRecognitionService.aidl @@ -20,6 +20,7 @@ import android.os.Bundle; import android.content.AttributionSource; import android.content.Intent; import android.speech.IRecognitionListener; +import android.speech.IRecognitionSupportCallback; /** * A Service interface to speech recognition. Call startListening when @@ -60,4 +61,18 @@ oneway interface IRecognitionService { * @param listener to receive callbacks, note that this must be non-null */ void cancel(in IRecognitionListener listener, boolean isShutdown); + + /** + * Checks whether this RecognitionService could {@link #startListening} successfully on the + * given recognizerIntent. For more information see {@link #startListening} and + * {@link RecognizerIntent}. + */ + void checkRecognitionSupport(in Intent recognizerIntent, in IRecognitionSupportCallback listener); + + /** + * Requests RecognitionService to download the support for the given recognizerIntent. For more + * information see {@link #checkRecognitionSupport}, {@link #startListening} and + * {@link RecognizerIntent}. + */ + void triggerModelDownload(in Intent recognizerIntent); } diff --git a/core/java/android/speech/IRecognitionSupportCallback.aidl b/core/java/android/speech/IRecognitionSupportCallback.aidl new file mode 100644 index 0000000000000..f5a54739af250 --- /dev/null +++ b/core/java/android/speech/IRecognitionSupportCallback.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.speech; + +import android.os.Bundle; +import android.speech.RecognitionSupport; + +/** + * Callback for speech recognition support checks, used with RecognitionService. + * This provides the {@link RecognitionSupport} for a given recognition request, callers can use + * it to check whether RecognitionService can fulfill a given recognition request. + * {@hide} + */ +oneway interface IRecognitionSupportCallback { + void onSupportResult(in RecognitionSupport recognitionSupport); + + /** + * A network or recognition error occurred. + * + * @param error code is defined in {@link SpeechRecognizer} + */ + void onError(in int error); +} diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index 5e647a4531bc5..5dbbc045077ef 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -37,6 +37,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; import com.android.internal.util.function.pooled.PooledLambda; @@ -90,6 +91,10 @@ public abstract class RecognitionService extends Service { private static final int MSG_RESET = 4; + private static final int MSG_CHECK_RECOGNITION_SUPPORT = 5; + + private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6; + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -107,6 +112,15 @@ public abstract class RecognitionService extends Service { case MSG_RESET: dispatchClearCallback(); break; + case MSG_CHECK_RECOGNITION_SUPPORT: + Pair intentAndListener = + (Pair) msg.obj; + dispatchCheckRecognitionSupport( + intentAndListener.first, intentAndListener.second); + break; + case MSG_TRIGGER_MODEL_DOWNLOAD: + dispatchTriggerModelDownload((Intent) msg.obj); + break; } } }; @@ -179,6 +193,15 @@ public abstract class RecognitionService extends Service { mStartedDataDelivery = false; } + private void dispatchCheckRecognitionSupport( + Intent intent, IRecognitionSupportCallback callback) { + RecognitionService.this.onCheckRecognitionSupport(intent, new SupportCallback(callback)); + } + + private void dispatchTriggerModelDownload(Intent intent) { + RecognitionService.this.triggerModelDownload(intent); + } + private class StartListeningArgs { public final Intent mIntent; @@ -238,6 +261,34 @@ public abstract class RecognitionService extends Service { */ protected abstract void onStopListening(Callback listener); + /** + * Queries the service on whether it would support a {@link #onStartListening(Intent, Callback)} + * for the same {@code recognizerIntent}. + * + *

The service will notify the caller about the level of support or error via + * {@link SupportCallback}. + * + *

If the service does not offer the support check it will notify the caller with + * {@link SpeechRecognizer#ERROR_CANNOT_CHECK_SUPPORT}. + */ + public void onCheckRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull SupportCallback supportCallback) { + if (DBG) { + Log.i(TAG, String.format("#onSupports [%s]", recognizerIntent)); + } + supportCallback.onError(SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT); + } + + /** + * Requests the download of the recognizer support for {@code recognizerIntent}. + */ + public void triggerModelDownload(@NonNull Intent recognizerIntent) { + if (DBG) { + Log.i(TAG, String.format("#downloadModel [%s]", recognizerIntent)); + } + } + @Override @SuppressLint("MissingNullability") public Context createContext(@NonNull ContextParams contextParams) { @@ -410,7 +461,45 @@ public abstract class RecognitionService extends Service { } } - /** Binder of the recognition service */ + /** + * This class receives callbacks from the speech recognition service and forwards them to the + * user. An instance of this class is passed to the + * {@link RecognitionService#onCheckRecognitionSupport(Intent, SupportCallback)} method. Recognizers may call + * these methods on any thread. + */ + public static class SupportCallback { + + private final IRecognitionSupportCallback mCallback; + + private SupportCallback(IRecognitionSupportCallback callback) { + this.mCallback = callback; + } + + /** The service should call this method to notify the caller about the level of support. */ + public void onSupportResult(@NonNull RecognitionSupport recognitionSupport) { + try { + mCallback.onSupportResult(recognitionSupport); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * The service should call this method when an error occurred and can't satisfy the support + * request. + * + * @param errorCode code is defined in {@link SpeechRecognizer} + */ + public void onError(@SpeechRecognizer.RecognitionError int errorCode) { + try { + mCallback.onError(errorCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + +/** Binder of the recognition service */ private static final class RecognitionServiceBinder extends IRecognitionService.Stub { private final WeakReference mServiceRef; @@ -452,6 +541,27 @@ public abstract class RecognitionService extends Service { } } + @Override + public void checkRecognitionSupport( + Intent recognizerIntent, IRecognitionSupportCallback callback) { + final RecognitionService service = mServiceRef.get(); + if (service != null) { + service.mHandler.sendMessage( + Message.obtain(service.mHandler, MSG_CHECK_RECOGNITION_SUPPORT, + Pair.create(recognizerIntent, callback))); + } + } + + @Override + public void triggerModelDownload(Intent recognizerIntent) { + final RecognitionService service = mServiceRef.get(); + if (service != null) { + service.mHandler.sendMessage( + Message.obtain( + service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent)); + } + } + public void clearReference() { mServiceRef.clear(); } diff --git a/core/java/android/speech/RecognitionSupport.aidl b/core/java/android/speech/RecognitionSupport.aidl new file mode 100644 index 0000000000000..20e52a8f04f58 --- /dev/null +++ b/core/java/android/speech/RecognitionSupport.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.speech; + +parcelable RecognitionSupport; diff --git a/core/java/android/speech/RecognitionSupport.java b/core/java/android/speech/RecognitionSupport.java new file mode 100644 index 0000000000000..3a86d0b306395 --- /dev/null +++ b/core/java/android/speech/RecognitionSupport.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.speech; + +import android.annotation.NonNull; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.List; + +/** Encodes the level of support for a given speech recognition request */ +@DataClass( + genConstructor = false, + genBuilder = true, + genEqualsHashCode = true, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true +) +public final class RecognitionSupport implements Parcelable { + + /** Support for this request is ready for use on this device for the returned languages. */ + @NonNull + private List mInstalledLanguages = null; + + /** Support for this request is scheduled for download for the returned languages. */ + @NonNull private List mPendingLanguages = null; + + /** These languages are supported but need to be downloaded before use. */ + @NonNull + private List mSupportedLanguages = null; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/speech/RecognitionSupport.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ RecognitionSupport( + @NonNull List installedLanguages, + @NonNull List pendingLanguages, + @NonNull List supportedLanguages) { + this.mInstalledLanguages = installedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mInstalledLanguages); + this.mPendingLanguages = pendingLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPendingLanguages); + this.mSupportedLanguages = supportedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSupportedLanguages); + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Support for this request is ready for use on this device for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull List getInstalledLanguages() { + return mInstalledLanguages; + } + + /** + * Support for this request is scheduled for download for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull List getPendingLanguages() { + return mPendingLanguages; + } + + /** + * These languages are supported but need to be downloaded before use. + */ + @DataClass.Generated.Member + public @NonNull List getSupportedLanguages() { + return mSupportedLanguages; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "RecognitionSupport { " + + "installedLanguages = " + mInstalledLanguages + ", " + + "pendingLanguages = " + mPendingLanguages + ", " + + "supportedLanguages = " + mSupportedLanguages + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(RecognitionSupport other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + RecognitionSupport that = (RecognitionSupport) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mInstalledLanguages, that.mInstalledLanguages) + && java.util.Objects.equals(mPendingLanguages, that.mPendingLanguages) + && java.util.Objects.equals(mSupportedLanguages, that.mSupportedLanguages); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mInstalledLanguages); + _hash = 31 * _hash + java.util.Objects.hashCode(mPendingLanguages); + _hash = 31 * _hash + java.util.Objects.hashCode(mSupportedLanguages); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeStringList(mInstalledLanguages); + dest.writeStringList(mPendingLanguages); + dest.writeStringList(mSupportedLanguages); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ RecognitionSupport(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + List installedLanguages = new java.util.ArrayList<>(); + in.readStringList(installedLanguages); + List pendingLanguages = new java.util.ArrayList<>(); + in.readStringList(pendingLanguages); + List supportedLanguages = new java.util.ArrayList<>(); + in.readStringList(supportedLanguages); + + this.mInstalledLanguages = installedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mInstalledLanguages); + this.mPendingLanguages = pendingLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPendingLanguages); + this.mSupportedLanguages = supportedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSupportedLanguages); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public RecognitionSupport[] newArray(int size) { + return new RecognitionSupport[size]; + } + + @Override + public RecognitionSupport createFromParcel(@NonNull android.os.Parcel in) { + return new RecognitionSupport(in); + } + }; + + /** + * A builder for {@link RecognitionSupport} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull List mInstalledLanguages; + private @NonNull List mPendingLanguages; + private @NonNull List mSupportedLanguages; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Support for this request is ready for use on this device for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull Builder setInstalledLanguages(@NonNull List value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mInstalledLanguages = value; + return this; + } + + /** @see #setInstalledLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addInstalledLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mInstalledLanguages == null) setInstalledLanguages(new java.util.ArrayList<>()); + mInstalledLanguages.add(value); + return this; + } + + /** + * Support for this request is scheduled for download for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull Builder setPendingLanguages(@NonNull List value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPendingLanguages = value; + return this; + } + + /** @see #setPendingLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addPendingLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mPendingLanguages == null) setPendingLanguages(new java.util.ArrayList<>()); + mPendingLanguages.add(value); + return this; + } + + /** + * These languages are supported but need to be downloaded before use. + */ + @DataClass.Generated.Member + public @NonNull Builder setSupportedLanguages(@NonNull List value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mSupportedLanguages = value; + return this; + } + + /** @see #setSupportedLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addSupportedLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mSupportedLanguages == null) setSupportedLanguages(new java.util.ArrayList<>()); + mSupportedLanguages.add(value); + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull RecognitionSupport build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mInstalledLanguages = null; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mPendingLanguages = null; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mSupportedLanguages = null; + } + RecognitionSupport o = new RecognitionSupport( + mInstalledLanguages, + mPendingLanguages, + mSupportedLanguages); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x8) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1639158640137L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/speech/RecognitionSupport.java", + inputSignatures = "private @android.annotation.NonNull java.util.List mInstalledLanguages\nprivate @android.annotation.NonNull java.util.List mPendingLanguages\nprivate @android.annotation.NonNull java.util.List mSupportedLanguages\nclass RecognitionSupport extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/speech/RecognitionSupportCallback.java b/core/java/android/speech/RecognitionSupportCallback.java new file mode 100644 index 0000000000000..9278e719ac816 --- /dev/null +++ b/core/java/android/speech/RecognitionSupportCallback.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.speech; + +import android.annotation.NonNull; + +/** + * Used for receiving notifications from the SpeechRecognizer about the device support status for + * the given recognition request. + */ +public interface RecognitionSupportCallback { + + /** Notifies the caller about the support for the given request. */ + void onSupportResult(@NonNull RecognitionSupport recognitionSupport); + + /** Notifies the caller about an error during the recognition support request */ + void onError(@SpeechRecognizer.RecognitionError int error); +} diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 3cdd8b8d8436b..71c1e882a1f68 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -38,6 +38,7 @@ import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.util.Slog; import com.android.internal.R; @@ -46,6 +47,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; @@ -112,7 +114,8 @@ public class SpeechRecognizer { ERROR_TOO_MANY_REQUESTS, ERROR_SERVER_DISCONNECTED, ERROR_LANGUAGE_NOT_SUPPORTED, - ERROR_LANGUAGE_UNAVAILABLE + ERROR_LANGUAGE_UNAVAILABLE, + ERROR_CANNOT_CHECK_SUPPORT, }) public @interface RecognitionError {} @@ -155,19 +158,24 @@ public class SpeechRecognizer { /** Requested language is supported, but not available currently (e.g. not downloaded yet). */ public static final int ERROR_LANGUAGE_UNAVAILABLE = 13; + /** The service does not allow to check for support. */ + public static final int ERROR_CANNOT_CHECK_SUPPORT = 14; + /** action codes */ private static final int MSG_START = 1; private static final int MSG_STOP = 2; private static final int MSG_CANCEL = 3; private static final int MSG_CHANGE_LISTENER = 4; 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; /** The actual RecognitionService endpoint */ private IRecognitionService mService; /** Context with which the manager was created */ private final Context mContext; - + /** Component to direct service intent to */ private final ComponentName mServiceComponent; @@ -197,6 +205,15 @@ public class SpeechRecognizer { case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT: handleSetTemporaryComponent((ComponentName) msg.obj); break; + case MSG_CHECK_RECOGNITION_SUPPORT: + Pair intentAndListener = + (Pair) msg.obj; + handleCheckRecognitionSupport( + intentAndListener.first, intentAndListener.second); + break; + case MSG_TRIGGER_MODEL_DOWNLOAD: + handleTriggerModelDownload((Intent) msg.obj); + break; } } }; @@ -208,7 +225,7 @@ public class SpeechRecognizer { private final Queue mPendingTasks = new LinkedBlockingQueue<>(); /** The Listener that will receive all the callbacks */ - private final InternalListener mListener = new InternalListener(); + private final InternalRecognitionListener mListener = new InternalRecognitionListener(); private final IBinder mClientToken = new Binder(); @@ -464,6 +481,38 @@ public class SpeechRecognizer { putMessage(Message.obtain(mHandler, MSG_CANCEL)); } + /** + * Checks whether {@code recognizerIntent} is supported by + * {@link SpeechRecognizer#startListening(Intent)}. + * + * @param recognizerIntent contains parameters for the recognition to be performed. The intent + * may also contain optional extras. See {@link RecognizerIntent} for the list of + * supported extras, any unlisted extra might be ignored. + * @param supportListener the listener on which to receive the support query results. + */ + public void checkRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull RecognitionSupportCallback supportListener) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + Objects.requireNonNull(supportListener, "listener must not be null"); + + putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT, + Pair.create(recognizerIntent, supportListener))); + } + + /** + * 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, RecognitionSupportCallback)}. + * + * @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"); + putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD)); + } + /** * Sets a temporary component to power on-device speech recognizer. * @@ -503,7 +552,7 @@ public class SpeechRecognizer { } try { mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); - if (DBG) Log.d(TAG, "service start listening command succeded"); + if (DBG) Log.d(TAG, "service start listening command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "startListening() failed", e); mListener.onError(ERROR_CLIENT); @@ -517,7 +566,7 @@ public class SpeechRecognizer { } try { mService.stopListening(mListener); - if (DBG) Log.d(TAG, "service stop listening command succeded"); + if (DBG) Log.d(TAG, "service stop listening command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "stopListening() failed", e); mListener.onError(ERROR_CLIENT); @@ -531,7 +580,7 @@ public class SpeechRecognizer { } try { mService.cancel(mListener, /*isShutdown*/ false); - if (DBG) Log.d(TAG, "service cancel command succeded"); + if (DBG) Log.d(TAG, "service cancel command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "cancel() failed", e); mListener.onError(ERROR_CLIENT); @@ -554,6 +603,35 @@ public class SpeechRecognizer { } } + private void handleCheckRecognitionSupport( + Intent recognizerIntent, RecognitionSupportCallback recognitionSupportCallback) { + if (!maybeInitializeManagerService()) { + return; + } + try { + mService.checkRecognitionSupport( + recognizerIntent, + new InternalSupportCallback(recognitionSupportCallback)); + if (DBG) Log.d(TAG, "service support command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "checkRecognitionSupport() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + + private void handleTriggerModelDownload(Intent recognizerIntent) { + if (!maybeInitializeManagerService()) { + return; + } + try { + mService.triggerModelDownload(recognizerIntent); + if (DBG) Log.d(TAG, "service download support command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "downloadModel() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + private boolean checkOpenConnection() { if (mService != null) { return true; @@ -626,7 +704,7 @@ public class SpeechRecognizer { } } - private boolean maybeInitializeManagerService() { + private synchronized boolean maybeInitializeManagerService() { if (mManagerService != null) { return true; } @@ -678,7 +756,7 @@ public class SpeechRecognizer { * Internal wrapper of IRecognitionListener which will propagate the results to * RecognitionListener */ - private static class InternalListener extends IRecognitionListener.Stub { + private static class InternalRecognitionListener extends IRecognitionListener.Stub { private RecognitionListener mInternalListener; private static final int MSG_BEGINNING_OF_SPEECH = 1; @@ -766,4 +844,42 @@ public class SpeechRecognizer { .sendToTarget(); } } + + private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub { + private final RecognitionSupportCallback mCallback; + + private static final int MSG_SUPPORT_RESULT = 1; + private static final int MSG_ERROR = 2; + + private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (mCallback == null) { + return; + } + switch (msg.what) { + case MSG_SUPPORT_RESULT: + mCallback.onSupportResult((RecognitionSupport) msg.obj); + break; + case MSG_ERROR: + mCallback.onError((Integer) msg.obj); + break; + } + } + }; + + private InternalSupportCallback(RecognitionSupportCallback callback) { + this.mCallback = callback; + } + + @Override + public void onSupportResult(RecognitionSupport recognitionSupport) throws RemoteException { + Message.obtain(mInternalHandler, MSG_SUPPORT_RESULT, recognitionSupport).sendToTarget(); + } + + @Override + public void onError(int errorCode) throws RemoteException { + Message.obtain(mInternalHandler, MSG_ERROR, errorCode).sendToTarget(); + } + } } diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java index 068626588745f..21d4cbbbcca76 100644 --- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; +import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Log; @@ -211,6 +212,30 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl service.checkRecognitionSupport(recognizerIntent, callback)); + } + + void triggerModelDownload(Intent recognizerIntent) { + if (!mConnected) { + Slog.e(TAG, "#downloadModel failed due to connection."); + return; + } + run(service -> service.triggerModelDownload(recognizerIntent)); + } + void shutdown() { synchronized (mLock) { if (this.mListener == null) { diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 5442e5b0413f6..ae23b9e46d238 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -33,6 +33,7 @@ import android.permission.PermissionManager; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; import android.speech.IRecognitionServiceManagerCallback; +import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Slog; @@ -169,6 +170,18 @@ final class SpeechRecognitionManagerServiceImpl extends clientToken.unlinkToDeath(deathRecipient, 0); } } + + @Override + public void checkRecognitionSupport( + Intent recognizerIntent, + IRecognitionSupportCallback callback) { + service.checkRecognitionSupport(recognizerIntent, callback); + } + + @Override + public void triggerModelDownload(Intent recognizerIntent) { + service.triggerModelDownload(recognizerIntent); + } }); } catch (RemoteException e) { Slog.e(TAG, "Error creating a speech recognition session", e);