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();
|
dispatchActivityDestroyed();
|
||||||
|
|
||||||
notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
|
notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
|
||||||
|
|
||||||
|
if (mUiTranslationController != null) {
|
||||||
|
mUiTranslationController.onActivityDestroyed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8450,9 +8454,8 @@ public class Activity extends ContextThemeWrapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@Override
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
|
public View findViewByAutofillIdTraversal(@NonNull AutofillId autofillId) {
|
||||||
final ArrayList<ViewRootImpl> roots =
|
final ArrayList<ViewRootImpl> roots =
|
||||||
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
|
WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
|
||||||
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
|
for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
|
||||||
@@ -8465,10 +8468,16 @@ public class Activity extends ContextThemeWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
|
||||||
|
return findViewByAutofillIdTraversal(autofillId);
|
||||||
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@Override
|
@Override
|
||||||
public final @NonNull boolean[] autofillClientGetViewVisibility(
|
public final @NonNull boolean[] autofillClientGetViewVisibility(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.service.translation.ITranslationCallback;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
@@ -36,9 +37,11 @@ import com.android.internal.util.SyncResultReceiver;
|
|||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
|
* 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.
|
// 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;
|
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.app.Activity;
|
||||||
import android.content.Context;
|
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.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.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
|
* @hide
|
||||||
*/
|
*/
|
||||||
public class UiTranslationController {
|
public class UiTranslationController {
|
||||||
|
|
||||||
private static final String TAG = "UiTranslationController";
|
private static final String TAG = "UiTranslationController";
|
||||||
|
@NonNull
|
||||||
private final Activity mActivity;
|
private final Activity mActivity;
|
||||||
|
@NonNull
|
||||||
private final Context mContext;
|
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) {
|
public UiTranslationController(Activity activity, Context context) {
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
mContext = context;
|
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.
|
* 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) {
|
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