Merge "Trigger newly-registered callbacks with events from currently active translations." into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
6d57b10190
@@ -163,7 +163,6 @@ public final class UiTranslationManager {
|
||||
/**
|
||||
* @removed Use {@link #startTranslation(TranslationSpec, TranslationSpec, List, ActivityId,
|
||||
* UiTranslationSpec)} instead.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
|
||||
@@ -180,13 +179,13 @@ public final class UiTranslationManager {
|
||||
/**
|
||||
* Request ui translation for a given Views.
|
||||
*
|
||||
* @param sourceSpec {@link TranslationSpec} for the data to be translated.
|
||||
* @param targetSpec {@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
|
||||
* @param sourceSpec {@link TranslationSpec} for the data to be translated.
|
||||
* @param targetSpec {@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
|
||||
* @param uiTranslationSpec configuration for translation of the specified views
|
||||
* @throws IllegalArgumentException if the no {@link View}'s {@link AutofillId} in the list
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
|
||||
@@ -220,8 +219,7 @@ 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}
|
||||
*
|
||||
* {@link android.app.assist.ActivityId#getToken()} is {@code null}
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
|
||||
@@ -245,8 +243,7 @@ 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}
|
||||
*
|
||||
* {@link android.app.assist.ActivityId#getToken()} is {@code null}
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
|
||||
@@ -270,8 +267,7 @@ 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}
|
||||
*
|
||||
* {@link android.app.assist.ActivityId#getToken()} is {@code null}
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)
|
||||
@@ -290,20 +286,29 @@ public final class UiTranslationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register for notifications of UI Translation state changes on the foreground activity. This
|
||||
* 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
|
||||
* {@link View#onCreateViewTranslationRequest(int[], Consumer)}.
|
||||
*
|
||||
* <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.
|
||||
* <p>
|
||||
* Starting from {@link android.os.Build.VERSION_CODES#TIRAMISU}, if Activities are already
|
||||
* being translated when a callback is registered, methods on the callback will be invoked for
|
||||
* each translated activity, depending on the state of translation:
|
||||
* <ul>
|
||||
* <li>If translation is <em>not</em> paused,
|
||||
* {@link UiTranslationStateCallback#onStarted} will be invoked.</li>
|
||||
* <li>If translation <em>is</em> paused, {@link UiTranslationStateCallback#onStarted}
|
||||
* will first be invoked, followed by {@link UiTranslationStateCallback#onPaused}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param callback the callback to register for receiving the state change
|
||||
* notifications
|
||||
* notifications
|
||||
*/
|
||||
public void registerUiTranslationStateCallback(
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@@ -355,9 +360,8 @@ public final class UiTranslationManager {
|
||||
* called or Activity is destroyed.
|
||||
*
|
||||
* @param activityDestroyed if the ui translation is finished because of activity destroyed.
|
||||
* @param activityId the identifier for the Activity which needs ui translation
|
||||
* @param componentName the ui translated Activity componentName.
|
||||
*
|
||||
* @param activityId the identifier for the Activity which needs ui translation
|
||||
* @param componentName the ui translated Activity componentName.
|
||||
* @hide
|
||||
*/
|
||||
public void onTranslationFinished(boolean activityDestroyed, ActivityId activityId,
|
||||
|
||||
@@ -225,12 +225,12 @@ public final class TranslationManagerService
|
||||
|
||||
@Override
|
||||
public void registerUiTranslationStateCallback(IRemoteCallback callback, int userId) {
|
||||
TranslationManagerServiceImpl service;
|
||||
synchronized (mLock) {
|
||||
service = getServiceForUserLocked(userId);
|
||||
}
|
||||
if (service != null) {
|
||||
service.registerUiTranslationStateCallback(callback, Binder.getCallingUid());
|
||||
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.registerUiTranslationStateCallbackLocked(callback,
|
||||
Binder.getCallingUid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ public final class TranslationManagerService
|
||||
|
||||
/**
|
||||
* Dump the service state into the given stream. You run "adb shell dumpsys translation".
|
||||
*/
|
||||
*/
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
|
||||
|
||||
@@ -22,9 +22,13 @@ import static android.view.translation.UiTranslationManager.EXTRA_SOURCE_LOCALE;
|
||||
import static android.view.translation.UiTranslationManager.EXTRA_STATE;
|
||||
import static android.view.translation.UiTranslationManager.EXTRA_TARGET_LOCALE;
|
||||
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.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
@@ -38,7 +42,9 @@ import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.service.translation.TranslationServiceInfo;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.translation.ITranslationServiceCallback;
|
||||
@@ -68,6 +74,8 @@ final class TranslationManagerServiceImpl extends
|
||||
AbstractPerUserSystemService<TranslationManagerServiceImpl, TranslationManagerService> {
|
||||
|
||||
private static final String TAG = "TranslationManagerServiceImpl";
|
||||
@SuppressLint("IsLoggableTagLength")
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
@@ -83,13 +91,19 @@ final class TranslationManagerServiceImpl extends
|
||||
@GuardedBy("mLock")
|
||||
private WeakReference<ActivityTokens> mLastActivityTokens;
|
||||
|
||||
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
|
||||
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
|
||||
|
||||
private final TranslationServiceRemoteCallback mRemoteServiceCallback =
|
||||
new TranslationServiceRemoteCallback();
|
||||
private final RemoteCallbackList<IRemoteCallback> mTranslationCapabilityCallbacks =
|
||||
new RemoteCallbackList<>();
|
||||
private final ArraySet<IBinder> mWaitingFinishedCallbackActivities = new ArraySet();
|
||||
private final ArraySet<IBinder> mWaitingFinishedCallbackActivities = new ArraySet<>();
|
||||
|
||||
/**
|
||||
* Key is translated activity uid, value is the specification and state for the translation.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<ActiveTranslation> mActiveTranslations = new SparseArray<>();
|
||||
|
||||
protected TranslationManagerServiceImpl(
|
||||
@NonNull TranslationManagerService master,
|
||||
@@ -231,6 +245,7 @@ final class TranslationManagerServiceImpl extends
|
||||
if (state == STATE_UI_TRANSLATION_FINISHED) {
|
||||
mWaitingFinishedCallbackActivities.add(token);
|
||||
}
|
||||
|
||||
IBinder activityToken = taskTopActivityTokens.getActivityToken();
|
||||
try {
|
||||
taskTopActivityTokens.getApplicationThread().updateUiTranslationState(
|
||||
@@ -243,9 +258,46 @@ final class TranslationManagerServiceImpl extends
|
||||
ComponentName componentName = mActivityTaskManagerInternal.getActivityName(activityToken);
|
||||
int translationActivityUid =
|
||||
getActivityUidByComponentName(getContext(), componentName, getUserId());
|
||||
String packageName = componentName.getPackageName();
|
||||
if (state != STATE_UI_TRANSLATION_FINISHED) {
|
||||
invokeCallbacks(state, sourceSpec, targetSpec, componentName.getPackageName(),
|
||||
invokeCallbacks(state, sourceSpec, targetSpec, packageName, translationActivityUid);
|
||||
updateActiveTranslations(state, sourceSpec, targetSpec, packageName,
|
||||
translationActivityUid);
|
||||
} else {
|
||||
if (mActiveTranslations.contains(translationActivityUid)) {
|
||||
mActiveTranslations.delete(translationActivityUid);
|
||||
} else {
|
||||
Slog.w(TAG, "Finishing translation for activity with uid=" + translationActivityUid
|
||||
+ " but no active translation was found for it");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateActiveTranslations(int state, TranslationSpec sourceSpec,
|
||||
TranslationSpec targetSpec, String packageName, int translationActivityUid) {
|
||||
// Keep track of active translations so that we can trigger callbacks that are
|
||||
// registered after translation has started.
|
||||
switch (state) {
|
||||
case STATE_UI_TRANSLATION_STARTED: {
|
||||
ActiveTranslation activeTranslation = new ActiveTranslation(sourceSpec,
|
||||
targetSpec, packageName);
|
||||
mActiveTranslations.put(translationActivityUid, activeTranslation);
|
||||
break;
|
||||
}
|
||||
case STATE_UI_TRANSLATION_PAUSED:
|
||||
case STATE_UI_TRANSLATION_RESUMED: {
|
||||
ActiveTranslation activeTranslation = mActiveTranslations.get(
|
||||
translationActivityUid);
|
||||
if (activeTranslation != null) {
|
||||
activeTranslation.isPaused = (state == STATE_UI_TRANSLATION_PAUSED);
|
||||
} else {
|
||||
Slog.w(TAG, "Pausing or resuming translation for activity with uid="
|
||||
+ translationActivityUid
|
||||
+ " but no active translation was found for it");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,49 +341,105 @@ final class TranslationManagerServiceImpl extends
|
||||
private void invokeCallbacks(
|
||||
int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName,
|
||||
int translationActivityUid) {
|
||||
Bundle res = new Bundle();
|
||||
res.putInt(EXTRA_STATE, state);
|
||||
// TODO(177500482): Store the locale pair so it can be sent for RESUME events.
|
||||
if (sourceSpec != null) {
|
||||
res.putSerializable(EXTRA_SOURCE_LOCALE, sourceSpec.getLocale());
|
||||
res.putSerializable(EXTRA_TARGET_LOCALE, targetSpec.getLocale());
|
||||
Bundle result = createResultForCallback(state, sourceSpec, targetSpec, packageName);
|
||||
if (mCallbacks.getRegisteredCallbackCount() == 0) {
|
||||
return;
|
||||
}
|
||||
res.putString(EXTRA_PACKAGE_NAME, packageName);
|
||||
// TODO(177500482): Only support the *current* Input Method.
|
||||
List<InputMethodInfo> enabledInputMethods =
|
||||
LocalServices.getService(InputMethodManagerInternal.class)
|
||||
.getEnabledInputMethodListAsUser(mUserId);
|
||||
List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods();
|
||||
mCallbacks.broadcast((callback, uid) -> {
|
||||
if ((int) uid == translationActivityUid) {
|
||||
try {
|
||||
callback.sendResult(res);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Code here is non-optimal since it's temporary..
|
||||
boolean isIme = false;
|
||||
for (InputMethodInfo inputMethod : enabledInputMethods) {
|
||||
if ((int) uid == inputMethod.getServiceInfo().applicationInfo.uid) {
|
||||
isIme = true;
|
||||
}
|
||||
}
|
||||
// TODO(177500482): Invoke it for the application being translated too.
|
||||
if (!isIme) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callback.sendResult(res);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e);
|
||||
}
|
||||
invokeCallback((int) uid, translationActivityUid, callback, result,
|
||||
enabledInputMethods);
|
||||
});
|
||||
}
|
||||
|
||||
public void registerUiTranslationStateCallback(IRemoteCallback callback, int sourceUid) {
|
||||
private List<InputMethodInfo> getEnabledInputMethods() {
|
||||
return LocalServices.getService(InputMethodManagerInternal.class)
|
||||
.getEnabledInputMethodListAsUser(mUserId);
|
||||
}
|
||||
|
||||
private Bundle createResultForCallback(
|
||||
int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName) {
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(EXTRA_STATE, state);
|
||||
// TODO(177500482): Store the locale pair so it can be sent for RESUME events.
|
||||
if (sourceSpec != null) {
|
||||
result.putSerializable(EXTRA_SOURCE_LOCALE, sourceSpec.getLocale());
|
||||
result.putSerializable(EXTRA_TARGET_LOCALE, targetSpec.getLocale());
|
||||
}
|
||||
result.putString(EXTRA_PACKAGE_NAME, packageName);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void invokeCallback(
|
||||
int callbackSourceUid, int translationActivityUid, IRemoteCallback callback,
|
||||
Bundle result, List<InputMethodInfo> enabledInputMethods) {
|
||||
if (callbackSourceUid == translationActivityUid) {
|
||||
// Invoke callback for the application being translated.
|
||||
try {
|
||||
callback.sendResult(result);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(177500482): Only support the *current* Input Method.
|
||||
// Code here is non-optimal since it's temporary..
|
||||
boolean isIme = false;
|
||||
for (InputMethodInfo inputMethod : enabledInputMethods) {
|
||||
if (callbackSourceUid == inputMethod.getServiceInfo().applicationInfo.uid) {
|
||||
isIme = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isIme) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callback.sendResult(result);
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
public void registerUiTranslationStateCallbackLocked(IRemoteCallback callback, int sourceUid) {
|
||||
mCallbacks.register(callback, sourceUid);
|
||||
// TODO(177500482): trigger the callback here if we're already translating the UI.
|
||||
|
||||
if (mActiveTranslations.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger the callback for already active translations.
|
||||
List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods();
|
||||
for (int i = 0; i < mActiveTranslations.size(); i++) {
|
||||
int activeTranslationUid = mActiveTranslations.keyAt(i);
|
||||
ActiveTranslation activeTranslation = mActiveTranslations.valueAt(i);
|
||||
if (activeTranslation == null) {
|
||||
continue;
|
||||
}
|
||||
String packageName = activeTranslation.packageName;
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Triggering callback for sourceUid=" + sourceUid
|
||||
+ " for translated activity with uid=" + activeTranslationUid
|
||||
+ "packageName=" + packageName + " isPaused=" + activeTranslation.isPaused);
|
||||
}
|
||||
|
||||
Bundle startedResult = createResultForCallback(STATE_UI_TRANSLATION_STARTED,
|
||||
activeTranslation.sourceSpec, activeTranslation.targetSpec,
|
||||
packageName);
|
||||
invokeCallback(sourceUid, activeTranslationUid, callback, startedResult,
|
||||
enabledInputMethods);
|
||||
if (activeTranslation.isPaused) {
|
||||
// Also send event so callback owners know that translation was started then paused.
|
||||
Bundle pausedResult = createResultForCallback(STATE_UI_TRANSLATION_PAUSED,
|
||||
activeTranslation.sourceSpec, activeTranslation.targetSpec,
|
||||
packageName);
|
||||
invokeCallback(sourceUid, activeTranslationUid, callback, pausedResult,
|
||||
enabledInputMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterUiTranslationStateCallback(IRemoteCallback callback) {
|
||||
@@ -376,4 +484,18 @@ final class TranslationManagerServiceImpl extends
|
||||
notifyClientsTranslationCapability(capability);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ActiveTranslation {
|
||||
public final TranslationSpec sourceSpec;
|
||||
public final TranslationSpec targetSpec;
|
||||
public final String packageName;
|
||||
public boolean isPaused = false;
|
||||
|
||||
private ActiveTranslation(TranslationSpec sourceSpec, TranslationSpec targetSpec,
|
||||
String packageName) {
|
||||
this.sourceSpec = sourceSpec;
|
||||
this.targetSpec = targetSpec;
|
||||
this.packageName = packageName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user