diff options
| author | Ahaan Ugale <augale@google.com> | 2021-03-16 05:44:45 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2021-03-16 05:44:45 +0000 |
| commit | 65509f464f680fc2db7e463846a271dd16596647 (patch) | |
| tree | 9d685193c54c4b7e342d66bb89970954208340ae /core/java/android | |
| parent | a6c9e47218db19f660c78612e3a78ca6e1a68e5d (diff) | |
| parent | 30704c1b853cb23564bf7f3f68baa80ddc5e91e8 (diff) | |
Merge "Add UiTranslationStateCallback." into sc-dev
Diffstat (limited to 'core/java/android')
4 files changed, 213 insertions, 3 deletions
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index df4ade09753b..d89c3d591d46 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -21,6 +21,7 @@ import android.util.ArrayMap; import android.util.Slog; import java.io.PrintWriter; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -354,6 +355,23 @@ public class RemoteCallbackList<E extends IInterface> { } /** + * Performs {@code action} on each callback and associated cookie, calling {@link + * #beginBroadcast()}/{@link #finishBroadcast()} before/after looping. + * + * @hide + */ + public <C> void broadcast(BiConsumer<E, C> action) { + int itemCount = beginBroadcast(); + try { + for (int i = 0; i < itemCount; i++) { + action.accept(getBroadcastItem(i), (C) getBroadcastCookie(i)); + } + } finally { + finishBroadcast(); + } + } + + /** * Returns the number of registered callbacks. Note that the number of registered * callbacks may differ from the value returned by {@link #beginBroadcast()} since * the former returns the number of callbacks registered at the time of the call diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index 7f6c4b474d3a..d347f31eb934 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -17,6 +17,7 @@ package android.view.translation; import android.os.IBinder; +import android.os.IRemoteCallback; import android.view.autofill.AutofillId; import android.view.translation.TranslationSpec; import com.android.internal.os.IResultReceiver; @@ -40,4 +41,7 @@ oneway interface ITranslationManager { void updateUiTranslationStateByTaskId(int state, in TranslationSpec sourceSpec, in TranslationSpec destSpec, in List<AutofillId> viewIds, int taskId, int userId); + + void registerUiTranslationStateCallback(in IRemoteCallback callback, int userId); + void unregisterUiTranslationStateCallback(in IRemoteCallback callback, int userId); } diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index 7c73e701b7c8..9fba95f29bdc 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -16,28 +16,36 @@ package android.view.translation; +import android.annotation.CallbackExecutor; 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.Binder; +import android.os.Bundle; +import android.os.IRemoteCallback; import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; import android.view.View; import android.view.autofill.AutofillId; +import com.android.internal.annotations.GuardedBy; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.Executor; +// TODO(b/178044703): Describe what UI Translation is. /** * The {@link UiTranslationManager} class provides ways for apps to use the ui translation * function in framework. - * - * @hide */ -@SystemApi public final class UiTranslationManager { private static final String TAG = "UiTranslationManager"; @@ -88,6 +96,14 @@ public final class UiTranslationManager { public @interface UiTranslationState { } + // Keys for the data transmitted in the internal UI Translation state callback. + /** @hide */ + public static final String EXTRA_STATE = "state"; + /** @hide */ + public static final String EXTRA_SOURCE_LOCALE = "source_locale"; + /** @hide */ + public static final String EXTRA_TARGET_LOCALE = "target_locale"; + @NonNull private final Context mContext; @@ -111,9 +127,12 @@ public final class UiTranslationManager { * @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 + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, int taskId) { @@ -141,8 +160,11 @@ public final class UiTranslationManager { * @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} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void startTranslation(@NonNull TranslationSpec sourceSpec, @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, @NonNull ActivityId activityId) { @@ -171,9 +193,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code finishTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void finishTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_FINISHED, @@ -191,8 +216,11 @@ public final class UiTranslationManager { * @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} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void finishTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -212,9 +240,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code pauseTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void pauseTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_PAUSED, @@ -232,8 +263,11 @@ public final class UiTranslationManager { * @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} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void pauseTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -253,9 +287,12 @@ public final class UiTranslationManager { * NOTE: Please use {@code resumeTranslation(ActivityId)} instead. * * @param taskId the Activity Task id which needs ui translation + * + * @hide */ // TODO, hide the APIs @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void resumeTranslation(int taskId) { try { mService.updateUiTranslationStateByTaskId(STATE_UI_TRANSLATION_RESUMED, @@ -273,8 +310,11 @@ public final class UiTranslationManager { * @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} + * + * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + @SystemApi public void resumeTranslation(@NonNull ActivityId activityId) { try { Objects.requireNonNull(activityId); @@ -286,4 +326,104 @@ public final class UiTranslationManager { throw e.rethrowFromSystemServer(); } } + + // TODO(b/178044703): Fix the View API link when it becomes public. + /** + * Register for notifications of UI Translation state changes on the foreground activity. This + * is available to the owning application itself and also the current input method. + * <p> + * The application whose UI is being translated can use this to customize the UI Translation + * behavior in ways that aren't made easy by methods like + * View#onCreateTranslationRequest(). + * <p> + * Input methods can use this to offer complementary features to UI Translation; for example, + * enabling outgoing message translation when the system is translating incoming messages in a + * communication app. + * + * @param callback the callback to register for receiving the state change + * notifications + */ + public void registerUiTranslationStateCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull UiTranslationStateCallback callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + synchronized (mCallbacks) { + if (mCallbacks.containsKey(callback)) { + Log.w(TAG, "registerUiTranslationStateCallback: callback already registered;" + + " ignoring."); + return; + } + final IRemoteCallback remoteCallback = + new UiTranslationStateRemoteCallback(executor, callback); + try { + mService.registerUiTranslationStateCallback(remoteCallback, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCallbacks.put(callback, remoteCallback); + } + } + + /** + * Unregister {@code callback}. + * + * @see #registerUiTranslationStateCallback(Executor, UiTranslationStateCallback) + */ + public void unregisterUiTranslationStateCallback(@NonNull UiTranslationStateCallback callback) { + Objects.requireNonNull(callback); + + synchronized (mCallbacks) { + final IRemoteCallback remoteCallback = mCallbacks.get(callback); + if (remoteCallback == null) { + Log.w(TAG, "unregisterUiTranslationStateCallback: callback not found; ignoring."); + return; + } + try { + mService.unregisterUiTranslationStateCallback(remoteCallback, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mCallbacks.remove(callback); + } + } + + @NonNull + @GuardedBy("mCallbacks") + private final Map<UiTranslationStateCallback, IRemoteCallback> mCallbacks = new ArrayMap<>(); + + private static class UiTranslationStateRemoteCallback extends IRemoteCallback.Stub { + private final Executor mExecutor; + private final UiTranslationStateCallback mCallback; + + UiTranslationStateRemoteCallback(Executor executor, + UiTranslationStateCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void sendResult(Bundle bundle) { + Binder.clearCallingIdentity(); + mExecutor.execute(() -> { + int state = bundle.getInt(EXTRA_STATE); + switch (state) { + case STATE_UI_TRANSLATION_STARTED: + case STATE_UI_TRANSLATION_RESUMED: + mCallback.onStarted( + bundle.getString(EXTRA_SOURCE_LOCALE), + bundle.getString(EXTRA_TARGET_LOCALE)); + break; + case STATE_UI_TRANSLATION_PAUSED: + mCallback.onPaused(); + break; + case STATE_UI_TRANSLATION_FINISHED: + mCallback.onFinished(); + break; + default: + Log.wtf(TAG, "Unexpected translation state:" + state); + } + }); + } + } } diff --git a/core/java/android/view/translation/UiTranslationStateCallback.java b/core/java/android/view/translation/UiTranslationStateCallback.java new file mode 100644 index 000000000000..1946b703935d --- /dev/null +++ b/core/java/android/view/translation/UiTranslationStateCallback.java @@ -0,0 +1,48 @@ +/* + * 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.annotation.NonNull; + +import java.util.concurrent.Executor; + +/** + * Callback for listening to UI Translation state changes. See {@link + * UiTranslationManager#registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)}. + */ +public interface UiTranslationStateCallback { + + /** + * The system is requesting translation of the UI from {@code sourceLocale} to {@code + * targetLocale}. + * <p> + * This is also called if either the requested {@code sourceLocale} or {@code targetLocale} has + * changed; or called again after {@link #onPaused()}. + */ + void onStarted(@NonNull String sourceLocale, @NonNull String targetLocale); + + /** + * The system is requesting that the application temporarily show the UI contents in their + * original language. + */ + void onPaused(); + + /** + * The UI Translation session has ended. + */ + void onFinished(); +} |
