Merge "API feedback: Translation View API refinement." into sc-dev

This commit is contained in:
Joanne Chung
2021-04-21 00:29:24 +00:00
committed by Android (Google) Code Review
9 changed files with 166 additions and 102 deletions

View File

@@ -48466,6 +48466,7 @@ package android.view {
method public boolean checkInputConnectionProxy(android.view.View);
method public void clearAnimation();
method public void clearFocus();
method public void clearViewTranslationCallback();
method public static int combineMeasuredStates(int, int);
method protected int computeHorizontalScrollExtent();
method protected int computeHorizontalScrollOffset();
@@ -48502,7 +48503,7 @@ package android.view {
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void dispatchProvideAutofillStructure(@NonNull android.view.ViewStructure, int);
method public void dispatchProvideStructure(android.view.ViewStructure);
method public void dispatchRequestTranslation(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @Nullable android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
method public void dispatchRequestTranslation(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @NonNull android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);
method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);
method public void dispatchScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>);
@@ -48699,7 +48700,7 @@ package android.view {
method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarTrackDrawable();
method public int getVerticalScrollbarWidth();
method @Nullable public android.view.ViewRoot getViewRoot();
method @Nullable public android.view.translation.ViewTranslationCallback getViewTranslationCallback();
method @Nullable public android.view.translation.ViewTranslationResponse getViewTranslationResponse();
method public android.view.ViewTreeObserver getViewTreeObserver();
method public int getVisibility();
method public final int getWidth();
@@ -48799,8 +48800,8 @@ package android.view {
method protected void onCreateContextMenu(android.view.ContextMenu);
method protected int[] onCreateDrawableState(int);
method public android.view.inputmethod.InputConnection onCreateInputConnection(android.view.inputmethod.EditorInfo);
method @Nullable public android.view.translation.ViewTranslationRequest onCreateTranslationRequest(@NonNull int[]);
method public void onCreateTranslationRequests(@NonNull long[], @NonNull int[], @NonNull java.util.function.Consumer<android.view.translation.ViewTranslationRequest>);
method public void onCreateViewTranslationRequest(@NonNull int[], @NonNull java.util.function.Consumer<android.view.translation.ViewTranslationRequest>);
method public void onCreateVirtualViewTranslationRequests(@NonNull long[], @NonNull int[], @NonNull java.util.function.Consumer<android.view.translation.ViewTranslationRequest>);
method @CallSuper protected void onDetachedFromWindow();
method protected void onDisplayHint(int);
method public boolean onDragEvent(android.view.DragEvent);
@@ -48845,8 +48846,8 @@ package android.view {
method public void onStartTemporaryDetach();
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
method public void onTranslationResponse(@NonNull android.view.translation.ViewTranslationResponse);
method public void onTranslationResponse(@NonNull android.util.LongSparseArray<android.view.translation.ViewTranslationResponse>);
method public void onViewTranslationResponse(@NonNull android.view.translation.ViewTranslationResponse);
method public void onVirtualViewTranslationResponses(@NonNull android.util.LongSparseArray<android.view.translation.ViewTranslationResponse>);
method @CallSuper public void onVisibilityAggregated(boolean);
method protected void onVisibilityChanged(@NonNull android.view.View, int);
method public void onWindowFocusChanged(boolean);

View File

@@ -14825,7 +14825,7 @@ package android.webkit {
method public default boolean onCheckIsTextEditor();
method public void onConfigurationChanged(android.content.res.Configuration);
method public android.view.inputmethod.InputConnection onCreateInputConnection(android.view.inputmethod.EditorInfo);
method public default void onCreateTranslationRequests(@NonNull long[], @NonNull int[], @NonNull java.util.function.Consumer<android.view.translation.ViewTranslationRequest>);
method public default void onCreateVirtualViewTranslationRequests(@NonNull long[], @NonNull int[], @NonNull java.util.function.Consumer<android.view.translation.ViewTranslationRequest>);
method public void onDetachedFromWindow();
method public boolean onDragEvent(android.view.DragEvent);
method public void onDraw(android.graphics.Canvas);
@@ -14850,7 +14850,7 @@ package android.webkit {
method public void onStartTemporaryDetach();
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
method public default void onTranslationResponse(@NonNull android.util.LongSparseArray<android.view.translation.ViewTranslationResponse>);
method public default void onVirtualViewTranslationResponses(@NonNull android.util.LongSparseArray<android.view.translation.ViewTranslationResponse>);
method public void onVisibilityChanged(android.view.View, int);
method public void onWindowFocusChanged(boolean);
method public void onWindowVisibilityChanged(int);

View File

@@ -5260,6 +5260,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Nullable
private ViewTranslationCallback mViewTranslationCallback;
@Nullable
private ViewTranslationResponse mViewTranslationResponse;
/**
* Simple constructor to use when creating a view from code.
*
@@ -30770,57 +30774,50 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
* Returns a {@link ViewTranslationRequest} which represents the content to be translated.
*
* <p>The default implementation does nothing and returns null.</p>
*
* @param supportedFormats the supported translation formats. For now, the only possible value
* is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
* @return the {@link ViewTranslationRequest} which contains the information to be translated or
* {@code null} if this View doesn't support translation.
* The {@link AutofillId} must be set on the returned value.
*/
@Nullable
public ViewTranslationRequest onCreateTranslationRequest(
@NonNull @DataFormat int[] supportedFormats) {
return null;
}
/**
* Returns a {@link ViewTranslationRequest} list which represents the content to be translated
* in the virtual view. This is called if this view returned a virtual view structure
* from {@link #onProvideContentCaptureStructure} and the system determined that those virtual
* views were relevant for translation.
* Collects a {@link ViewTranslationRequest} which represents the content to be translated in
* the view.
*
* <p>The default implementation does nothing.</p>
*
* @param virtualChildIds the virtual child ids which represents the child views in the virtual
* @param supportedFormats the supported translation formats. For now, the only possible value
* is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
* @param requestsCollector a {@link ViewTranslationRequest} collector that can be used to
* collect the information to be translated in the view. The {@code requestsCollector} only
* accepts one request; an IllegalStateException is thrown if more than one
* {@link ViewTranslationRequest} is submitted to it. The {@link AutofillId} must be set on the
* {@link ViewTranslationRequest}.
*/
public void onCreateViewTranslationRequest(@NonNull @DataFormat int[] supportedFormats,
@NonNull Consumer<ViewTranslationRequest> requestsCollector) {
}
/**
* Collects {@link ViewTranslationRequest}s which represents the content to be translated
* for the virtual views in the host view. This is called if this view returned a virtual
* view structure from {@link #onProvideContentCaptureStructure} and the system determined that
* those virtual views were relevant for translation.
*
* <p>The default implementation does nothing.</p>
*
* @param virtualIds the virtual view ids which represents the virtual views in the host
* view.
* @param supportedFormats the supported translation formats. For now, the only possible value
* is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
* @param requestsCollector a {@link ViewTranslationRequest} collector that can be called
* multiple times to collect the information to be translated in the virtual view. One
* multiple times to collect the information to be translated in the host view. One
* {@link ViewTranslationRequest} per virtual child. The {@link ViewTranslationRequest} must
* contains the {@link AutofillId} corresponding to the virtualChildIds. Do not keep this
* Consumer after the method returns.
*/
@SuppressLint("NullableCollection")
public void onCreateTranslationRequests(@NonNull long[] virtualChildIds,
public void onCreateVirtualViewTranslationRequests(@NonNull long[] virtualIds,
@NonNull @DataFormat int[] supportedFormats,
@NonNull Consumer<ViewTranslationRequest> requestsCollector) {
// no-op
}
/**
* Returns a {@link ViewTranslationCallback} that is used to display/hide the translated
* information. If the View supports displaying translated content, it should implement
* {@link ViewTranslationCallback}.
*
* <p>The default implementation returns null if developers don't set the customized
* {@link ViewTranslationCallback} by {@link #setViewTranslationCallback} </p>
*
* @return a {@link ViewTranslationCallback} that is used to control how to display the
* translated information or {@code null} if this View doesn't support translation.
* @hide
*/
@Nullable
public ViewTranslationCallback getViewTranslationCallback() {
@@ -30840,30 +30837,52 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
* Called when the content from {@link View#onCreateTranslationRequest} had been translated by
* the TranslationService.
* Clear the {@link ViewTranslationCallback} from this view.
*/
public void clearViewTranslationCallback() {
mViewTranslationCallback = null;
}
/**
* Returns the {@link ViewTranslationResponse} associated with this view. The response will be
* set when the translation is done then {@link #onViewTranslationResponse} is called. The
* {@link ViewTranslationCallback} can use to get {@link ViewTranslationResponse} to display the
* translated information.
*
* <p> The default implementation does nothing.</p>
* @return a {@link ViewTranslationResponse} that contains the translated information associated
* with this view or {@code null} if this View doesn't have the translation.
*/
@Nullable
public ViewTranslationResponse getViewTranslationResponse() {
return mViewTranslationResponse;
}
/**
* Called when the content from {@link View#onCreateViewTranslationRequest} had been translated
* by the TranslationService.
*
* <p> The default implementation will set the ViewTranslationResponse that can be get from
* {@link View#getViewTranslationResponse}. </p>
*
* @param response a {@link ViewTranslationResponse} that contains the translated information
* which can be shown in the view.
*/
public void onTranslationResponse(@NonNull ViewTranslationResponse response) {
// no-op
public void onViewTranslationResponse(@NonNull ViewTranslationResponse response) {
mViewTranslationResponse = response;
}
/**
* Called when the content from {@link View#onCreateTranslationRequest} had been translated by
* the TranslationService.
* Called when the content from {@link View#onCreateVirtualViewTranslationRequests} had been
* translated by the TranslationService.
*
* <p> The default implementation does nothing.</p>
*
* @param response a {@link ViewTranslationResponse} SparseArray for the request that send by
* {@link View#onCreateTranslationRequests} that contains the translated information which can
* be shown in the view. The key of SparseArray is
* the virtual child ids.
* {@link View#onCreateVirtualViewTranslationRequests} that contains the translated information
* which can be shown in the view. The key of SparseArray is the virtual child ids.
*/
public void onTranslationResponse(@NonNull LongSparseArray<ViewTranslationResponse> response) {
public void onVirtualViewTranslationResponses(
@NonNull LongSparseArray<ViewTranslationResponse> response) {
// no-op
}
@@ -30871,16 +30890,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Dispatch to collect the {@link ViewTranslationRequest}s for translation purpose by traversing
* the hierarchy when the app requests ui translation. Typically, this method should only be
* overridden by subclasses that provide a view hierarchy (such as {@link ViewGroup}). Other
* classes should override {@link View#onCreateTranslationRequest}. When requested to start the
* ui translation, the system will call this method to traverse the view hierarchy to call
* {@link View#onCreateTranslationRequest} to build {@link ViewTranslationRequest}s and create a
* classes should override {@link View#onCreateViewTranslationRequest} for normal view or
* override {@link View#onVirtualViewTranslationResponses} for view contains virtual children.
* When requested to start the ui translation, the system will call this method to traverse the
* view hierarchy to collect {@link ViewTranslationRequest}s and create a
* {@link android.view.translation.Translator} to translate the requests. All the
* {@link ViewTranslationRequest}s must be added when the traversal is done.
*
* <p> The default implementation calls {@link View#onCreateTranslationRequest} to build
* {@link ViewTranslationRequest} if the view should be translated. The view is marked as having
* {@link #setHasTransientState(boolean) transient state} so that recycling of views doesn't
* prevent the system from attaching the response to it.</p>
* <p> The default implementation calls {@link View#onCreateViewTranslationRequest} for normal
* view or calls {@link View#onVirtualViewTranslationResponses} for view contains virtual
* children to build {@link ViewTranslationRequest} if the view should be translated.
* The view is marked as having {@link #setHasTransientState(boolean) transient state} so that
* recycling of views doesn't prevent the system from attaching the response to it.</p>
*
* @param viewIds a map for the view's {@link AutofillId} and its virtual child ids or
* {@code null} if the view doesn't have virtual child that should be translated. The virtual
@@ -30893,27 +30914,47 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void dispatchRequestTranslation(@NonNull Map<AutofillId, long[]> viewIds,
@NonNull @DataFormat int[] supportedFormats,
@Nullable TranslationCapability capability,
@NonNull TranslationCapability capability,
@NonNull List<ViewTranslationRequest> requests) {
AutofillId autofillId = getAutofillId();
if (viewIds.containsKey(autofillId)) {
if (viewIds.get(autofillId) == null) {
ViewTranslationRequest request = onCreateTranslationRequest(supportedFormats);
if (request != null && request.getKeys().size() > 0) {
requests.add(request);
if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
Log.v(CONTENT_CAPTURE_LOG_TAG, "Calling setHasTransientState(true) for "
+ autofillId);
}
// TODO: Add a default ViewTranslationCallback for View that resets this in
// onClearTranslation(). Also update the javadoc for this method to mention
// that.
setHasTransientState(true);
}
// TODO: avoiding the allocation per view
onCreateViewTranslationRequest(supportedFormats,
new ViewTranslationRequestConsumer(requests));
} else {
onCreateTranslationRequests(viewIds.get(autofillId), supportedFormats, request -> {
requests.add(request);
});
onCreateVirtualViewTranslationRequests(viewIds.get(autofillId), supportedFormats,
request -> {
requests.add(request);
});
}
}
}
private class ViewTranslationRequestConsumer implements Consumer<ViewTranslationRequest> {
private final List<ViewTranslationRequest> mRequests;
private boolean mCalled;
ViewTranslationRequestConsumer(List<ViewTranslationRequest> requests) {
mRequests = requests;
}
@Override
public void accept(ViewTranslationRequest request) {
if (mCalled) {
throw new IllegalStateException("The translation Consumer is not reusable.");
}
mCalled = true;
if (request != null && request.getKeys().size() > 0) {
mRequests.add(request);
if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) {
Log.v(CONTENT_CAPTURE_LOG_TAG, "Calling setHasTransientState(true) for "
+ getAutofillId());
}
// TODO: Add a default ViewTranslationCallback for View that resets this in
// onClearTranslation(). Also update the javadoc for this method to mention
// that.
setHasTransientState(true);
}
}
}

View File

@@ -315,7 +315,7 @@ public class UiTranslationController {
}
return;
}
view.onTranslationResponse(virtualChildResponse);
view.onVirtualViewTranslationResponses(virtualChildResponse);
if (view.getViewTranslationCallback() != null) {
view.getViewTranslationCallback().onShowTranslation(view);
}
@@ -373,8 +373,7 @@ public class UiTranslationController {
// TODO: Do this for specific views on request only.
callback.enableContentPadding();
view.onTranslationResponse(response);
view.onViewTranslationResponse(response);
callback.onShowTranslation(view);
});
}

View File

@@ -21,23 +21,27 @@ import android.annotation.UiThread;
import android.view.View;
/**
* Callback for handling the translated information show or hide in the {@link View}. See {@link
* View#onTranslationResponse} for how to get the translated information.
* Callback for handling the translated information show or hide in the {@link View}.
*/
@UiThread
public interface ViewTranslationCallback {
/**
* Called when the translated text is ready to show or if the user has requested to reshow the
* translated content after hiding it. This may be called before the translation results are
* returned through the {@link View#onTranslationResponse}.
* translated content after hiding it.
* <p>
* The translated content can be obtained from {@link View#getViewTranslationResponse}. This
* method will not be called before {@link View#onViewTranslationResponse} or
* {@link View#onVirtualViewTranslationResponses}.
*
* See {@link View#onViewTranslationResponse} for how to get the translated information.
*
* @return {@code true} if the View handles showing the translation.
*/
boolean onShowTranslation(@NonNull View view);
/**
* Called when the user wants to show the original text instead of the translated text. This
* may be called before the translation results are returned through the
* {@link View#onTranslationResponse}.
* method will not be called before {@link View#onViewTranslationResponse} or
* {@link View#onViewTranslationResponse}.
*
* @return {@code true} if the View handles hiding the translation.
*/

View File

@@ -2861,16 +2861,17 @@ public class WebView extends AbsoluteLayout
@Override
@Nullable
public void onCreateTranslationRequests(@NonNull long[] virtualChildIds,
public void onCreateVirtualViewTranslationRequests(@NonNull long[] virtualIds,
@NonNull @DataFormat int[] supportedFormats,
@NonNull Consumer<ViewTranslationRequest> requestsCollector) {
mProvider.getViewDelegate().onCreateTranslationRequests(virtualChildIds, supportedFormats,
requestsCollector);
mProvider.getViewDelegate().onCreateVirtualViewTranslationRequests(virtualIds,
supportedFormats, requestsCollector);
}
@Override
public void onTranslationResponse(@NonNull LongSparseArray<ViewTranslationResponse> response) {
mProvider.getViewDelegate().onTranslationResponse(response);
public void onVirtualViewTranslationResponses(
@NonNull LongSparseArray<ViewTranslationResponse> response) {
mProvider.getViewDelegate().onVirtualViewTranslationResponses(response);
}
/** @hide */

View File

@@ -365,14 +365,14 @@ public interface WebViewProvider {
}
@SuppressLint("NullableCollection")
default void onCreateTranslationRequests(
@NonNull @SuppressWarnings("unused") long[] virtualChildIds,
default void onCreateVirtualViewTranslationRequests(
@NonNull @SuppressWarnings("unused") long[] virtualIds,
@NonNull @SuppressWarnings("unused") @DataFormat int[] supportedFormats,
@NonNull @SuppressWarnings("unused")
Consumer<ViewTranslationRequest> requestsCollector) {
}
default void onTranslationResponse(
default void onVirtualViewTranslationResponses(
@NonNull @SuppressWarnings("unused")
LongSparseArray<ViewTranslationResponse> response) {
}

View File

@@ -13870,7 +13870,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
* Returns a {@link ViewTranslationRequest} which represents the content to be translated.
* Collects a {@link ViewTranslationRequest} which represents the content to be translated in
* the view.
*
* <p>NOTE: When overriding the method, it should not translate the password. If the subclass
* uses {@link TransformationMethod} to display the translated result, it's also not recommend
@@ -13880,15 +13881,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
* @return the {@link ViewTranslationRequest} which contains the information to be translated.
*/
@Nullable
@Override
public ViewTranslationRequest onCreateTranslationRequest(@NonNull int[] supportedFormats) {
public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats,
@NonNull Consumer<ViewTranslationRequest> requestsCollector) {
if (supportedFormats == null || supportedFormats.length == 0) {
// TODO(b/182433547): remove before S release
if (UiTranslationController.DEBUG) {
Log.w(LOG_TAG, "Do not provide the support translation formats.");
}
return null;
return;
}
ViewTranslationRequest.Builder requestBuilder =
new ViewTranslationRequest.Builder(getAutofillId());
@@ -13899,7 +13900,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (UiTranslationController.DEBUG) {
Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
}
return null;
return;
}
boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
// TODO(b/177214256): support selectable text translation.
@@ -13914,14 +13915,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Log.w(LOG_TAG, "Cannot create translation request. editable = "
+ isTextEditable() + ", isPassword = " + isPassword + ", selectable = "
+ isTextSelectable());
return;
}
return null;
}
// TODO(b/176488462): apply the view's important for translation
requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
TranslationRequestValue.forText(mText));
}
return requestBuilder.build();
requestsCollector.accept(requestBuilder.build());
}
/**
@@ -13932,6 +13933,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @return a {@link ViewTranslationCallback} that is used to control how to display the
* translated information or {@code null} if this View doesn't support translation.
*
* @hide
*/
@Nullable
@Override
@@ -13948,15 +13951,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
*
* Called when the content from {@link #onCreateTranslationRequest} had been translated by the
* TranslationService. The default implementation will replace the current
* Called when the content from {@link #onCreateViewTranslationRequest} had been translated by
* the TranslationService. The default implementation will replace the current
* {@link TransformationMethod} to transform the original text to the translated text display.
*
* @param response a {@link ViewTranslationResponse} that contains the translated information
* which can be shown in the view.
*/
@Override
public void onTranslationResponse(@NonNull ViewTranslationResponse response) {
public void onViewTranslationResponse(@NonNull ViewTranslationResponse response) {
// set ViewTranslationResponse
super.onViewTranslationResponse(response);
// TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default
// implementation if the view has overridden getViewTranslationCallback.
TextViewTranslationCallback callback =

View File

@@ -79,6 +79,11 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
@Override
public boolean onShowTranslation(@NonNull View view) {
mIsShowingTranslation = true;
if (view.getViewTranslationResponse() == null) {
Log.wtf(TAG, "onShowTranslation() shouldn't be called before "
+ "onViewTranslationResponse().");
return false;
}
if (mTranslationTransformation != null) {
((TextView) view).setTransformationMethod(mTranslationTransformation);
} else {
@@ -86,6 +91,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
// TODO(b/182433547): remove before S release
Log.w(TAG, "onShowTranslation(): no translated text.");
}
return false;
}
return true;
}
@@ -96,6 +102,11 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
@Override
public boolean onHideTranslation(@NonNull View view) {
mIsShowingTranslation = false;
if (view.getViewTranslationResponse() == null) {
Log.wtf(TAG, "onHideTranslation() shouldn't be called before "
+ "onViewTranslationResponse().");
return false;
}
// Restore to original text content.
if (mTranslationTransformation != null) {
((TextView) view).setTransformationMethod(
@@ -105,6 +116,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
// TODO(b/182433547): remove before S release
Log.w(TAG, "onHideTranslation(): no translated text.");
}
return false;
}
return true;
}
@@ -124,6 +136,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
// TODO(b/182433547): remove before S release
Log.w(TAG, "onClearTranslation(): no translated text.");
}
return false;
}
return true;
}