Merge "Implement the behavior after receiving auto translation request." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e95f58c7bd
@@ -2669,6 +2669,10 @@ public class Activity extends ContextThemeWrapper
|
||||
dispatchActivityDestroyed();
|
||||
|
||||
notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
|
||||
|
||||
if (mUiTranslationController != null) {
|
||||
mUiTranslationController.onActivityDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8450,9 +8454,8 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@Nullable
|
||||
public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
|
||||
public View findViewByAutofillIdTraversal(@NonNull AutofillId autofillId) {
|
||||
final ArrayList<ViewRootImpl> roots =
|
||||
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
|
||||
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
|
||||
@@ -8465,10 +8468,16 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
@Nullable
|
||||
public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
|
||||
return findViewByAutofillIdTraversal(autofillId);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public final @NonNull boolean[] autofillClientGetViewVisibility(
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.service.translation.ITranslationCallback;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -36,9 +37,11 @@ import com.android.internal.util.SyncResultReceiver;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
|
||||
@@ -295,4 +298,49 @@ public class Translator {
|
||||
}
|
||||
|
||||
// TODO: add methods for UI-toolkit case.
|
||||
/** @hide */
|
||||
public void requestUiTranslate(@NonNull List<TranslationRequest> requests,
|
||||
@NonNull Consumer<TranslationResponse> responseCallback) {
|
||||
if (mDirectServiceBinder == null) {
|
||||
Log.wtf(TAG, "Translator created without proper initialization.");
|
||||
return;
|
||||
}
|
||||
final android.service.translation.TranslationRequest request =
|
||||
new android.service.translation.TranslationRequest
|
||||
.Builder(getNextRequestId(), mSourceSpec, mDestSpec, requests)
|
||||
.build();
|
||||
final ITranslationCallback callback =
|
||||
new TranslationResponseCallbackImpl(responseCallback);
|
||||
try {
|
||||
mDirectServiceBinder.onTranslationRequest(request, mId, callback, null);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "RemoteException calling flushRequest");
|
||||
}
|
||||
}
|
||||
|
||||
private static class TranslationResponseCallbackImpl extends ITranslationCallback.Stub {
|
||||
|
||||
private final WeakReference<Consumer<TranslationResponse>> mResponseCallback;
|
||||
|
||||
TranslationResponseCallbackImpl(Consumer<TranslationResponse> responseCallback) {
|
||||
mResponseCallback = new WeakReference<>(responseCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTranslationComplete(TranslationResponse response) throws RemoteException {
|
||||
provideTranslationResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() throws RemoteException {
|
||||
provideTranslationResponse(null);
|
||||
}
|
||||
|
||||
private void provideTranslationResponse(TranslationResponse response) {
|
||||
final Consumer<TranslationResponse> responseCallback = mResponseCallback.get();
|
||||
if (responseCallback != null) {
|
||||
responseCallback.accept(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,35 +16,274 @@
|
||||
|
||||
package android.view.translation;
|
||||
|
||||
import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_FINISHED;
|
||||
import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_PAUSED;
|
||||
import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_RESUMED;
|
||||
import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_STARTED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.WorkerThread;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.translation.UiTranslationManager.UiTranslationState;
|
||||
|
||||
import com.android.internal.util.function.pooled.PooledLambda;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A controller to manage the ui translation requests.
|
||||
* A controller to manage the ui translation requests for the {@link Activity}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class UiTranslationController {
|
||||
|
||||
private static final String TAG = "UiTranslationController";
|
||||
|
||||
@NonNull
|
||||
private final Activity mActivity;
|
||||
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
@NonNull
|
||||
private final Object mLock = new Object();
|
||||
|
||||
// Each Translator is distinguished by sourceSpec and desSepc.
|
||||
@NonNull
|
||||
private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Translator> mTranslators;
|
||||
@NonNull
|
||||
private final ArrayMap<AutofillId, WeakReference<View>> mViews;
|
||||
@NonNull
|
||||
private final HandlerThread mWorkerThread;
|
||||
@NonNull
|
||||
private final Handler mWorkerHandler;
|
||||
|
||||
public UiTranslationController(Activity activity, Context context) {
|
||||
mActivity = activity;
|
||||
mContext = context;
|
||||
mViews = new ArrayMap<>();
|
||||
mTranslators = new ArrayMap<>();
|
||||
|
||||
mWorkerThread =
|
||||
new HandlerThread("UiTranslationController_" + mActivity.getComponentName(),
|
||||
Process.THREAD_PRIORITY_FOREGROUND);
|
||||
mWorkerThread.start();
|
||||
mWorkerHandler = mWorkerThread.getThreadHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Ui translation state.
|
||||
*/
|
||||
public void updateUiTranslationState(int state, TranslationSpec sourceSpec,
|
||||
public void updateUiTranslationState(@UiTranslationState int state, TranslationSpec sourceSpec,
|
||||
TranslationSpec destSpec, List<AutofillId> views) {
|
||||
// Implement it. Deal with the each states
|
||||
if (!mActivity.isResumed()) {
|
||||
return;
|
||||
}
|
||||
switch (state) {
|
||||
case STATE_UI_TRANSLATION_STARTED:
|
||||
final Pair<TranslationSpec, TranslationSpec> specs =
|
||||
new Pair<>(sourceSpec, destSpec);
|
||||
if (!mTranslators.containsKey(specs)) {
|
||||
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
|
||||
UiTranslationController::createTranslatorAndStart,
|
||||
UiTranslationController.this, sourceSpec, destSpec, views));
|
||||
} else {
|
||||
onUiTranslationStarted(mTranslators.get(specs), views);
|
||||
}
|
||||
break;
|
||||
case STATE_UI_TRANSLATION_PAUSED:
|
||||
runForEachView((view) -> view.onPauseUiTranslation(), STATE_UI_TRANSLATION_PAUSED);
|
||||
break;
|
||||
case STATE_UI_TRANSLATION_RESUMED:
|
||||
runForEachView((view) -> view.onRestoreUiTranslation(),
|
||||
STATE_UI_TRANSLATION_PAUSED);
|
||||
break;
|
||||
case STATE_UI_TRANSLATION_FINISHED:
|
||||
destroyTranslators();
|
||||
runForEachView((view) -> view.onFinishUiTranslation(), STATE_UI_TRANSLATION_PAUSED);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "onAutoTranslationStateChange(): unknown state: " + state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Activity is destroyed.
|
||||
*/
|
||||
public void onActivityDestroyed() {
|
||||
synchronized (mLock) {
|
||||
mViews.clear();
|
||||
destroyTranslators();
|
||||
mWorkerThread.quitSafely();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method is used by {@link Translator}, it will be called when the translation is done. The
|
||||
* translation result can be get from here.
|
||||
*/
|
||||
public void onTranslationCompleted(TranslationResponse response) {
|
||||
if (response == null || response.getTranslationStatus()
|
||||
!= TranslationResponse.TRANSLATION_STATUS_SUCCESS) {
|
||||
Log.w(TAG, "Fail result from TranslationService, response: " + response);
|
||||
return;
|
||||
}
|
||||
final List<TranslationRequest> translatedResult = response.getTranslations();
|
||||
onTranslationCompleted(translatedResult);
|
||||
}
|
||||
|
||||
private void onTranslationCompleted(List<TranslationRequest> translatedResult) {
|
||||
if (!mActivity.isResumed()) {
|
||||
return;
|
||||
}
|
||||
final int resultCount = translatedResult.size();
|
||||
synchronized (mLock) {
|
||||
for (int i = 0; i < resultCount; i++) {
|
||||
final TranslationRequest request = translatedResult.get(i);
|
||||
final AutofillId autofillId = request.getAutofillId();
|
||||
if (autofillId == null) {
|
||||
continue;
|
||||
}
|
||||
final View view = mViews.get(autofillId).get();
|
||||
if (view == null) {
|
||||
Log.w(TAG, "onTranslationCompleted: the Veiew for autofill id " + autofillId
|
||||
+ " may be gone.");
|
||||
continue;
|
||||
}
|
||||
mActivity.runOnUiThread(() -> view.onTranslationComplete(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is an ui translation request comes to request view translation.
|
||||
*/
|
||||
@WorkerThread
|
||||
private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec destSpec,
|
||||
List<AutofillId> views) {
|
||||
// Create Translator
|
||||
final Translator translator = createTranslatorIfNeeded(sourceSpec, destSpec);
|
||||
if (translator == null) {
|
||||
Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " destSpec:"
|
||||
+ destSpec);
|
||||
return;
|
||||
}
|
||||
onUiTranslationStarted(translator, views);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void sendTranslationRequest(Translator translator,
|
||||
ArrayList<TranslationRequest> requests) {
|
||||
translator.requestUiTranslate(requests, this::onTranslationCompleted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is an ui translation request comes to request view translation.
|
||||
*/
|
||||
private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
|
||||
synchronized (mLock) {
|
||||
if (views == null || views.size() == 0) {
|
||||
throw new IllegalArgumentException("Invalid empty views: " + views);
|
||||
}
|
||||
// Find Views collect the translation data
|
||||
// TODO(b/178084101): try to optimize, e.g. to this in a single traversal
|
||||
final int viewCounts = views.size();
|
||||
final ArrayList<TranslationRequest> requests = new ArrayList<>();
|
||||
for (int i = 0; i < viewCounts; i++) {
|
||||
final AutofillId viewAutofillId = views.get(i);
|
||||
final View view = mActivity.findViewByAutofillIdTraversal(viewAutofillId);
|
||||
if (view == null) {
|
||||
Log.w(TAG, "Can not find the View for autofill id= " + viewAutofillId);
|
||||
continue;
|
||||
}
|
||||
mViews.put(viewAutofillId, new WeakReference<>(view));
|
||||
mActivity.runOnUiThread(() -> {
|
||||
final TranslationRequest translationRequest = view.onCreateTranslationRequest();
|
||||
if (translationRequest != null
|
||||
&& translationRequest.getTranslationText().length() > 0) {
|
||||
requests.add(translationRequest);
|
||||
}
|
||||
if (requests.size() == viewCounts) {
|
||||
Log.v(TAG, "onUiTranslationStarted: send " + requests.size() + " request.");
|
||||
mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
|
||||
UiTranslationController::sendTranslationRequest,
|
||||
UiTranslationController.this, translator, requests));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runForEachView(Consumer<View> action, @UiTranslationState int state) {
|
||||
synchronized (mLock) {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
final int viewCounts = mViews.size();
|
||||
for (int i = 0; i < viewCounts; i++) {
|
||||
final View view = mViews.valueAt(i).get();
|
||||
if (view == null) {
|
||||
Log.w(TAG, "The View for autofill id " + mViews.keyAt(i)
|
||||
+ " may be gone for state " + stateToString(state));
|
||||
continue;
|
||||
}
|
||||
action.accept(view);
|
||||
}
|
||||
if (state == STATE_UI_TRANSLATION_FINISHED) {
|
||||
mViews.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Translator createTranslatorIfNeeded(
|
||||
TranslationSpec sourceSpec, TranslationSpec destSpec) {
|
||||
final TranslationManager tm = mContext.getSystemService(TranslationManager.class);
|
||||
if (tm == null) {
|
||||
Log.e(TAG, "Can not find TranslationManager when trying to create translator.");
|
||||
return null;
|
||||
}
|
||||
final Translator translator = tm.createTranslator(sourceSpec, destSpec);
|
||||
if (translator != null) {
|
||||
final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
|
||||
mTranslators.put(specs, translator);
|
||||
}
|
||||
return translator;
|
||||
}
|
||||
|
||||
private void destroyTranslators() {
|
||||
synchronized (mLock) {
|
||||
final int count = mTranslators.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Translator translator = mTranslators.valueAt(i);
|
||||
translator.destroy();
|
||||
}
|
||||
mTranslators.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the state.
|
||||
*/
|
||||
public static String stateToString(@UiTranslationState int state) {
|
||||
switch (state) {
|
||||
case STATE_UI_TRANSLATION_STARTED:
|
||||
return "UI_TRANSLATION_STARTED";
|
||||
case STATE_UI_TRANSLATION_PAUSED:
|
||||
return "UI_TRANSLATION_PAUSED";
|
||||
case STATE_UI_TRANSLATION_RESUMED:
|
||||
return "UI_TRANSLATION_RESUMED";
|
||||
case STATE_UI_TRANSLATION_FINISHED:
|
||||
return "UI_TRANSLATION_FINISHED";
|
||||
default:
|
||||
return "Unknown state (" + state + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user