From e5c140a59da55246ce669c8e497a8445b010c705 Mon Sep 17 00:00:00 2001 From: Adam He Date: Mon, 12 Apr 2021 13:30:25 -0700 Subject: [PATCH] Add api to propagating translation capability updates. This change adda the apia in the TranslationService that allows to update the TranslationCapability to the registered clients. This change doesn't contain the register part in the TranslationManager, the change will be done on the next change. Bug: 176208267 Test: atest CtsTranslationTestCases Test: manual verification CTS-Coverage-Bug: 182990474 Change-Id: Iec7b3dc30f99985415162394d0e64d8e825ea5d9 --- core/api/current.txt | 1 + core/api/system-current.txt | 1 + .../translation/ITranslationService.aidl | 3 +- .../translation/TranslationService.java | 36 ++++++++++++++++-- .../ITranslationServiceCallback.aidl | 28 ++++++++++++++ .../translation/TranslationCapability.java | 29 +++++++++++---- .../view/translation/TranslationManager.java | 27 +++++++++++++- .../translation/RemoteTranslationService.java | 9 +++-- .../TranslationManagerServiceImpl.java | 37 ++++++++++++++++++- 9 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 core/java/android/view/translation/ITranslationServiceCallback.aidl diff --git a/core/api/current.txt b/core/api/current.txt index a40e0f65e0e1f..17add3acb22e0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -52771,6 +52771,7 @@ package android.view.translation { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int STATE_AVAILABLE_TO_DOWNLOAD = 1; // 0x1 field public static final int STATE_DOWNLOADING = 2; // 0x2 + field public static final int STATE_NOT_AVAILABLE = 4; // 0x4 field public static final int STATE_ON_DEVICE = 3; // 0x3 } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ee31e85921946..80e861cad2b80 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10405,6 +10405,7 @@ package android.service.translation { method public abstract void onFinishTranslationSession(int); method public abstract void onTranslationCapabilitiesRequest(int, int, @NonNull java.util.function.Consumer>); method public abstract void onTranslationRequest(@NonNull android.view.translation.TranslationRequest, int, @Nullable android.os.CancellationSignal, @NonNull android.service.translation.TranslationService.OnTranslationResultCallback); + method public final void updateTranslationCapability(@NonNull android.view.translation.TranslationCapability); field public static final String SERVICE_INTERFACE = "android.service.translation.TranslationService"; field public static final String SERVICE_META_DATA = "android.translation_service"; } diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl index 297f00a4d753a..e9dd2c3bd70b4 100644 --- a/core/java/android/service/translation/ITranslationService.aidl +++ b/core/java/android/service/translation/ITranslationService.aidl @@ -16,6 +16,7 @@ package android.service.translation; +import android.os.IBinder; import android.os.ResultReceiver; import android.view.translation.TranslationContext; import com.android.internal.os.IResultReceiver; @@ -30,7 +31,7 @@ import com.android.internal.os.IResultReceiver; * @hide */ oneway interface ITranslationService { - void onConnected(); + void onConnected(in IBinder callback); void onDisconnected(); void onCreateTranslationSession(in TranslationContext translationContext, int sessionId, in IResultReceiver receiver); diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java index fe86c0b86e47b..f67c64ef87631 100644 --- a/core/java/android/service/translation/TranslationService.java +++ b/core/java/android/service/translation/TranslationService.java @@ -41,6 +41,7 @@ import android.os.ResultReceiver; import android.util.ArraySet; import android.util.Log; import android.view.translation.ITranslationDirectManager; +import android.view.translation.ITranslationServiceCallback; import android.view.translation.TranslationCapability; import android.view.translation.TranslationContext; import android.view.translation.TranslationManager; @@ -50,6 +51,7 @@ import android.view.translation.TranslationSpec; import com.android.internal.os.IResultReceiver; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; @@ -84,15 +86,17 @@ public abstract class TranslationService extends Service { public static final String SERVICE_META_DATA = "android.translation_service"; private Handler mHandler; + private ITranslationServiceCallback mCallback; + /** * Binder to receive calls from system server. */ private final ITranslationService mInterface = new ITranslationService.Stub() { @Override - public void onConnected() { - mHandler.sendMessage(obtainMessage(TranslationService::onConnected, - TranslationService.this)); + public void onConnected(IBinder callback) { + mHandler.sendMessage(obtainMessage(TranslationService::handleOnConnected, + TranslationService.this, callback)); } @Override @@ -273,6 +277,32 @@ public abstract class TranslationService extends Service { @TranslationSpec.DataFormat int targetFormat, @NonNull Consumer> callback); + /** + * Called by the service to notify an update in existing {@link TranslationCapability}s. + * + * @param capability the updated {@link TranslationCapability} with its new states and flags. + */ + public final void updateTranslationCapability(@NonNull TranslationCapability capability) { + Objects.requireNonNull(capability, "translation capability should not be null"); + + final ITranslationServiceCallback callback = mCallback; + if (callback == null) { + Log.w(TAG, "updateTranslationCapability(): no server callback"); + return; + } + + try { + callback.updateTranslationCapability(capability); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + private void handleOnConnected(@NonNull IBinder callback) { + mCallback = ITranslationServiceCallback.Stub.asInterface(callback); + onConnected(); + } + // TODO(b/176464808): Need to handle client dying case private void handleOnCreateTranslationSession(@NonNull TranslationContext translationContext, diff --git a/core/java/android/view/translation/ITranslationServiceCallback.aidl b/core/java/android/view/translation/ITranslationServiceCallback.aidl new file mode 100644 index 0000000000000..eb89b49535de5 --- /dev/null +++ b/core/java/android/view/translation/ITranslationServiceCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 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.view.translation; + +import android.view.translation.TranslationCapability; + +/** + * Interface from the Translation service to the system. + * + * @hide + */ +oneway interface ITranslationServiceCallback { + void updateTranslationCapability(in TranslationCapability capability); +} diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java index 28b21138eef10..0bea1212e3e2f 100644 --- a/core/java/android/view/translation/TranslationCapability.java +++ b/core/java/android/view/translation/TranslationCapability.java @@ -48,6 +48,16 @@ public final class TranslationCapability implements Parcelable { * TODO: fill in javadoc */ public static final @ModelState int STATE_ON_DEVICE = 3; + /** + * The translation service does not support translation between the source and target specs. + * + *

Note: This state is not returned from calling + * {@link TranslationManager#getOnDeviceTranslationCapabilities}. This state will only appear as + * part of capability updates from + * {@link TranslationManager#addOnDeviceTranslationCapabilityUpdateListener} if existing support + * was dropped.

+ */ + public static final @ModelState int STATE_NOT_AVAILABLE = 4; /** * The state of translation readiness between {@code mSourceSpec} and {@code mTargetSpec}. @@ -103,7 +113,7 @@ public final class TranslationCapability implements Parcelable { - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -120,7 +130,8 @@ public final class TranslationCapability implements Parcelable { @IntDef(prefix = "STATE_", value = { STATE_AVAILABLE_TO_DOWNLOAD, STATE_DOWNLOADING, - STATE_ON_DEVICE + STATE_ON_DEVICE, + STATE_NOT_AVAILABLE }) @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member @@ -136,6 +147,8 @@ public final class TranslationCapability implements Parcelable { return "STATE_DOWNLOADING"; case STATE_ON_DEVICE: return "STATE_ON_DEVICE"; + case STATE_NOT_AVAILABLE: + return "STATE_NOT_AVAILABLE"; default: return Integer.toHexString(value); } } @@ -238,12 +251,14 @@ public final class TranslationCapability implements Parcelable { if (!(mState == STATE_AVAILABLE_TO_DOWNLOAD) && !(mState == STATE_DOWNLOADING) - && !(mState == STATE_ON_DEVICE)) { + && !(mState == STATE_ON_DEVICE) + && !(mState == STATE_NOT_AVAILABLE)) { throw new java.lang.IllegalArgumentException( "state was " + mState + " but must be one of: " + "STATE_AVAILABLE_TO_DOWNLOAD(" + STATE_AVAILABLE_TO_DOWNLOAD + "), " + "STATE_DOWNLOADING(" + STATE_DOWNLOADING + "), " - + "STATE_ON_DEVICE(" + STATE_ON_DEVICE + ")"); + + "STATE_ON_DEVICE(" + STATE_ON_DEVICE + "), " + + "STATE_NOT_AVAILABLE(" + STATE_NOT_AVAILABLE + ")"); } this.mSourceSpec = sourceSpec; @@ -275,10 +290,10 @@ public final class TranslationCapability implements Parcelable { }; @DataClass.Generated( - time = 1616438309593L, - codegenVersion = "1.0.22", + time = 1619119609557L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java", - inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)") + inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_NOT_AVAILABLE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java index b61eab97779e2..d9dfd15442253 100644 --- a/core/java/android/view/translation/TranslationManager.java +++ b/core/java/android/view/translation/TranslationManager.java @@ -22,7 +22,10 @@ import android.annotation.SystemService; import android.annotation.WorkerThread; import android.app.PendingIntent; import android.content.Context; +import android.os.Binder; +import android.os.Bundle; import android.os.Handler; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.RemoteException; import android.os.SynchronousResultReceiver; @@ -40,6 +43,7 @@ import java.util.Collections; import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; @@ -301,8 +305,6 @@ public final class TranslationManager { sourceFormat, targetFormat, pendingIntent); } - //TODO: Add method to propagate updates to mTCapabilityUpdateListeners - /** * Returns an immutable PendingIntent which can be used to launch an activity to view/edit * on-device translation settings. @@ -353,4 +355,25 @@ public final class TranslationManager { return sAvailableRequestId; } } + + private static class TranslationCapabilityRemoteCallback extends + IRemoteCallback.Stub { + private final Executor mExecutor; + + TranslationCapabilityRemoteCallback(Executor executor) { + mExecutor = executor; + } + + @Override + public void sendResult(Bundle bundle) { + Binder.withCleanCallingIdentity( + () -> mExecutor.execute(() -> onTranslationCapabilityUpdate(bundle))); + } + + private void onTranslationCapabilityUpdate(Bundle bundle) { + TranslationCapability capability = (TranslationCapability) bundle.getParcelable( + EXTRA_CAPABILITIES); + //TODO: Implement after deciding how capability listeners are implemented. + } + } } diff --git a/services/translation/java/com/android/server/translation/RemoteTranslationService.java b/services/translation/java/com/android/server/translation/RemoteTranslationService.java index 82cb728f5bd22..c77396d8371b5 100644 --- a/services/translation/java/com/android/server/translation/RemoteTranslationService.java +++ b/services/translation/java/com/android/server/translation/RemoteTranslationService.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.os.IBinder; import android.os.ResultReceiver; import android.service.translation.ITranslationService; import android.service.translation.TranslationService; @@ -44,8 +45,10 @@ final class RemoteTranslationService extends ServiceConnector.Impl mTranslationCapabilityCallbacks = + new RemoteCallbackList<>(); + protected TranslationManagerServiceImpl( @NonNull TranslationManagerService master, @NonNull Object lock, int userId, boolean disabled) { @@ -117,8 +125,8 @@ final class TranslationManagerServiceImpl extends return null; } final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName); - mRemoteTranslationService = new RemoteTranslationService(getContext(), - serviceComponent, mUserId, /* isInstantAllowed= */ false); + mRemoteTranslationService = new RemoteTranslationService(getContext(), serviceComponent, + mUserId, /* isInstantAllowed= */ false, mRemoteServiceCallback); } return mRemoteTranslationService; } @@ -242,4 +250,29 @@ final class TranslationManagerServiceImpl extends final String packageName = mTranslationServiceInfo.getServiceInfo().packageName; return new ComponentName(packageName, activityName); } + + private void notifyClientsTranslationCapability(TranslationCapability capability) { + final Bundle res = new Bundle(); + res.putParcelable(EXTRA_CAPABILITIES, capability); + mTranslationCapabilityCallbacks.broadcast((callback, uid) -> { + try { + callback.sendResult(res); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e); + } + }); + } + + private final class TranslationServiceRemoteCallback extends + ITranslationServiceCallback.Stub { + + @Override + public void updateTranslationCapability(TranslationCapability capability) { + if (capability == null) { + Slog.wtf(TAG, "received a null TranslationCapability from TranslationService."); + return; + } + notifyClientsTranslationCapability(capability); + } + } }