From 19006134b3e84554ed6d737ff44502b7eeeb2463 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Mon, 1 Feb 2021 22:59:09 +0800 Subject: [PATCH] Update UiTranslationManager APIs to use contentcapture token parameter 1. Update UiTranslationManager APIs to use ActivityId parameter 2. Keep old APIs definition until AiAi is ready for new APIs. Bug: 177789967 Test: manual. Local add test app in allow list, and get token from local content capture service and use it in UiTranslationManager api. Change-Id: I96aa5704600fd9b81eff7a2f42eefe78a43112da --- core/api/system-current.txt | 4 + .../view/translation/ITranslationManager.aidl | 6 +- .../translation/UiTranslationManager.java | 134 +++++++++++++++--- .../TranslationManagerService.java | 24 +++- .../TranslationManagerServiceImpl.java | 37 ++++- 5 files changed, 178 insertions(+), 27 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c7744a415db59..4085da628f6ab 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -14062,9 +14062,13 @@ package android.view.translation { public final class UiTranslationManager { method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId); method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List, @NonNull android.app.assist.ActivityId); } } diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index e1754531d761f..872e15e3d43e8 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -36,6 +36,10 @@ oneway interface ITranslationManager { int sessionId, in IResultReceiver receiver, int userId); void updateUiTranslationState(int state, in TranslationSpec sourceSpec, - in TranslationSpec destSpec, in List viewIds, in int taskId, + in TranslationSpec destSpec, in List viewIds, IBinder token, int taskId, + int userId); + // deprecated + void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec, + in TranslationSpec destSpec, in List viewIds, int taskId, int userId); } diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index eeb463ae0ed38..a3a6a2e521380 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.app.assist.ActivityId; import android.content.Context; import android.os.RemoteException; import android.view.View; @@ -95,26 +96,61 @@ public final class UiTranslationManager { /** * Request ui translation for a given Views. * + * NOTE: Please use {@code startTranslation(TranslationSpec, TranslationSpec, List, + * ActivityId)} instead. + * * @param sourceSpec {@link TranslationSpec} for the data to be translated. * @param destSpec {@link TranslationSpec} for the translated data. * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List viewIds, int taskId) { - // TODO(b/177789967): Return result code or find a way to notify the status. - // TODO(b/177394471): The is a temparary API, the expected is requestUiTranslation( - // TranslationSpec, TranslationSpec,List, Binder). We may need more time to - // implement it, use task id as initial version for demo. Objects.requireNonNull(sourceSpec); Objects.requireNonNull(destSpec); Objects.requireNonNull(viewIds); + if (viewIds.size() == 0) { + throw new IllegalArgumentException("Invalid empty views: " + viewIds); + } + try { + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_STARTED, sourceSpec, + destSpec, viewIds, taskId, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** + * Request ui translation for a given Views. + * + * @param sourceSpec {@link TranslationSpec} for the data to be translated. + * @param destSpec {@link TranslationSpec} for the translated data. + * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated + * @param activityId the identifier for the Activity which needs ui translation + * @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list + * @throws NullPointerException the sourceSpec, destSpec, viewIds, activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void startTranslation(@NonNull TranslationSpec sourceSpec, + @NonNull TranslationSpec destSpec, @NonNull List viewIds, + @NonNull ActivityId activityId) { + // TODO(b/177789967): Return result code or find a way to notify the status. + Objects.requireNonNull(sourceSpec); + Objects.requireNonNull(destSpec); + Objects.requireNonNull(viewIds); + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + if (viewIds.size() == 0) { + throw new IllegalArgumentException("Invalid empty views: " + viewIds); + } try { mService.updateUiTranslationState(STATE_UI_TRANSLATION_STARTED, sourceSpec, - destSpec, viewIds, taskId, mContext.getUserId()); + destSpec, viewIds, activityId.getToken(), activityId.getTaskId(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -124,14 +160,56 @@ public final class UiTranslationManager { * Request to disable the ui translation. It will destroy all the {@link Translator}s and no * longer to show to show the translated text. * + * NOTE: Please use {@code finishTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is finishUiTranslation( - // Binder). We may need more time to implement it, use task id as initial version. + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, + mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to disable the ui translation. It will destroy all the {@link Translator}s and no + * longer to show to show the translated text. + * + * @param activityId the identifier for the Activity which needs ui translation + * @throws NullPointerException the activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void finishTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to pause the current ui translation's {@link Translator} which will switch back to + * the original language. + * + * NOTE: Please use {@code pauseTranslation(ActivityId)} instead. + * + * @param taskId the Activity Task id which needs ui translation + */ + // TODO, hide the APIs + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void pauseTranslation(int taskId) { + try { + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { @@ -143,16 +221,18 @@ public final class UiTranslationManager { * Request to pause the current ui translation's {@link Translator} which will switch back to * the original language. * - * @param taskId the Activity Task id which needs ui translation + * @param activityId the identifier for the Activity which needs ui translation + * @throws NullPointerException the activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) - public void pauseTranslation(int taskId) { + public void pauseTranslation(@NonNull ActivityId activityId) { try { - // TODO(b/177394471): The is a temparary API, the expected is pauseUiTranslation(Binder) - // We may need more time to implement it, use task id as initial version for demo + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED, - null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, - mContext.getUserId()); + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -162,18 +242,40 @@ public final class UiTranslationManager { * Request to resume the paused ui translation's {@link Translator} which will switch to the * translated language if the text had been translated. * + * NOTE: Please use {@code resumeTranslation(ActivityId)} instead. + * * @param taskId the Activity Task id which needs ui translation */ + // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int taskId) { try { - // TODO(b/177394471): The is a temparary API, the expected is resumeUiTranslation( - // Binder). We may need more time to implement it, use task id as initial version. - mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED, + mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED, null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + + /** + * Request to resume the paused ui translation's {@link Translator} which will switch to the + * translated language if the text had been translated. + * + * @param activityId the identifier for the Activity which needs ui translation + * @throws NullPointerException the activityId or + * {@link android.app.assist.ActivityId#getToken()} is {@code null} + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void resumeTranslation(@NonNull ActivityId activityId) { + try { + Objects.requireNonNull(activityId); + Objects.requireNonNull(activityId.getToken()); + mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + activityId.getToken(), activityId.getTaskId(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java index b6244b8fb93b0..8874e0afd7165 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerService.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java @@ -26,6 +26,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -171,16 +172,33 @@ public final class TranslationManagerService } @Override - public void updateUiTranslationState(@UiTranslationState int state, + public void updateUiTranslationStateByTaskId(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, List viewIds, int taskId, int userId) { + // deprecated + enforceCallerHasPermission(MANAGE_UI_TRANSLATION); + synchronized (mLock) { + final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null && (isDefaultServiceLocked(userId) + || isCalledByServiceAppLocked(userId, + "updateUiTranslationStateByTaskId"))) { + service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds, + taskId); + } + } + } + + @Override + public void updateUiTranslationState(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List viewIds, + IBinder token, int taskId, int userId) { enforceCallerHasPermission(MANAGE_UI_TRANSLATION); synchronized (mLock) { final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); if (service != null && (isDefaultServiceLocked(userId) || isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) { - service.updateUiTranslationState(state, sourceSpec, destSpec, viewIds, - taskId); + service.updateUiTranslationStateLocked(state, sourceSpec, destSpec, viewIds, + token, taskId); } } } diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index 38be85c921978..ab6ac12c90fa7 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.IBinder; import android.os.RemoteException; import android.service.translation.TranslationServiceInfo; import android.util.Slog; @@ -133,18 +134,40 @@ final class TranslationManagerServiceImpl extends } @GuardedBy("mLock") - public void updateUiTranslationState(@UiTranslationState int state, + public void updateUiTranslationStateLocked(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, List viewIds, int taskId) { - // TODO(b/177394471): use taskId as a temporary solution. The solution may use a token to - // content capture manager service find the activitytoken. Then we can use this - // activitytoken to find the activity to callback. But we need to change cc API so use - // temporary solution. - final ActivityTokens tokens = mActivityTaskManagerInternal.getTopActivityForTask(taskId); - if (tokens == null) { + // deprecated + final ActivityTokens taskTopActivityTokens = + mActivityTaskManagerInternal.getTopActivityForTask(taskId); + if (taskTopActivityTokens == null) { Slog.w(TAG, "Unknown activity to query for update translation state."); return; } + updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec, + viewIds); + } + + @GuardedBy("mLock") + public void updateUiTranslationStateLocked(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List viewIds, + IBinder token, int taskId) { + // Get top activity for a given task id + final ActivityTokens taskTopActivityTokens = + mActivityTaskManagerInternal.getTopActivityForTask(taskId); + if (taskTopActivityTokens == null + || taskTopActivityTokens.getShareableActivityToken() != token) { + Slog.w(TAG, "Unknown activity or it was finished to query for update " + + "translation state for token=" + token + " taskId=" + taskId); + return; + } + updateUiTranslationStateByActivityTokens(taskTopActivityTokens, state, sourceSpec, destSpec, + viewIds); + } + + private void updateUiTranslationStateByActivityTokens(ActivityTokens tokens, + @UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, + List viewIds) { try { tokens.getApplicationThread().updateUiTranslationState(tokens.getActivityToken(), state, sourceSpec, destSpec, viewIds);