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
This commit is contained in:
Adam He
2021-04-12 13:30:25 -07:00
parent 9753f5c383
commit e5c140a59d
9 changed files with 153 additions and 18 deletions

View File

@@ -52771,6 +52771,7 @@ package android.view.translation {
field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationCapability> 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
}

View File

@@ -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<java.util.Set<android.view.translation.TranslationCapability>>);
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";
}

View File

@@ -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);

View File

@@ -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<Set<TranslationCapability>> 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,

View File

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

View File

@@ -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.
*
* <p>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.</p>
*/
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() {}

View File

@@ -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.
}
}
}

View File

@@ -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<ITranslationS
private final int mRequestTimeoutMs;
private final ComponentName mComponentName;
private final IBinder mRemoteCallback;
RemoteTranslationService(Context context, ComponentName serviceName,
int userId, boolean bindInstantServiceAllowed) {
int userId, boolean bindInstantServiceAllowed, IBinder callback) {
super(context,
new Intent(TranslationService.SERVICE_INTERFACE).setComponent(serviceName),
bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
@@ -53,7 +56,7 @@ final class RemoteTranslationService extends ServiceConnector.Impl<ITranslationS
mIdleUnbindTimeoutMs = TIMEOUT_IDLE_UNBIND_MS;
mRequestTimeoutMs = TIMEOUT_REQUEST_MS;
mComponentName = serviceName;
mRemoteCallback = callback;
// Bind right away.
connect();
}
@@ -67,7 +70,7 @@ final class RemoteTranslationService extends ServiceConnector.Impl<ITranslationS
boolean connected) {
try {
if (connected) {
service.onConnected();
service.onConnected(mRemoteCallback);
} else {
service.onDisconnected();
}

View File

@@ -16,6 +16,7 @@
package com.android.server.translation;
import static android.view.translation.TranslationManager.EXTRA_CAPABILITIES;
import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE;
import static android.view.translation.UiTranslationManager.EXTRA_STATE;
import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE;
@@ -35,6 +36,8 @@ import android.service.translation.TranslationServiceInfo;
import android.util.Slog;
import android.view.autofill.AutofillId;
import android.view.inputmethod.InputMethodInfo;
import android.view.translation.ITranslationServiceCallback;
import android.view.translation.TranslationCapability;
import android.view.translation.TranslationContext;
import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationManager.UiTranslationState;
@@ -67,6 +70,11 @@ final class TranslationManagerServiceImpl extends
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final TranslationServiceRemoteCallback mRemoteServiceCallback =
new TranslationServiceRemoteCallback();
private final RemoteCallbackList<IRemoteCallback> 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);
}
}
}