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); + } + } }