Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0ceced4 authored by Mark Punzalan's avatar Mark Punzalan Committed by Automerger Merge Worker
Browse files

Merge "Trigger newly-registered callbacks with events from currently active...

Merge "Trigger newly-registered callbacks with events from currently active translations." into tm-dev am: 6d57b101

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17388847



Change-Id: Iff0cf9d1772f48570009c96b81a093668f12ff41
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 6557b8cb 6d57b101
Loading
Loading
Loading
Loading
+22 −18
Original line number Diff line number Diff line
@@ -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)
@@ -182,11 +181,11 @@ public final class UiTranslationManager {
     *
     * @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 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)
@@ -221,7 +220,6 @@ 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)
@@ -246,7 +244,6 @@ 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)
@@ -271,7 +268,6 @@ 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)
@@ -290,17 +286,26 @@ 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
@@ -357,7 +362,6 @@ public final class UiTranslationManager {
     * @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.
     *
     * @hide
     */
    public void onTranslationFinished(boolean activityDestroyed, ActivityId activityId,
+6 −6
Original line number Diff line number Diff line
@@ -225,12 +225,12 @@ public final class TranslationManagerService

        @Override
        public void registerUiTranslationStateCallback(IRemoteCallback callback, int userId) {
            TranslationManagerServiceImpl service;
            synchronized (mLock) {
                service = getServiceForUserLocked(userId);
            }
                final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
                if (service != null) {
                service.registerUiTranslationStateCallback(callback, Binder.getCallingUid());
                    service.registerUiTranslationStateCallbackLocked(callback,
                            Binder.getCallingUid());
                }
            }
        }

+158 −36
Original line number Diff line number Diff line
@@ -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);
        Bundle result = createResultForCallback(state, sourceSpec, targetSpec, packageName);
        if (mCallbacks.getRegisteredCallbackCount() == 0) {
            return;
        }
        List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods();
        mCallbacks.broadcast((callback, uid) -> {
            invokeCallback((int) uid, translationActivityUid, callback, result,
                    enabledInputMethods);
        });
    }

    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) {
            res.putSerializable(EXTRA_SOURCE_LOCALE, sourceSpec.getLocale());
            res.putSerializable(EXTRA_TARGET_LOCALE, targetSpec.getLocale());
            result.putSerializable(EXTRA_SOURCE_LOCALE, sourceSpec.getLocale());
            result.putSerializable(EXTRA_TARGET_LOCALE, targetSpec.getLocale());
        }
        res.putString(EXTRA_PACKAGE_NAME, packageName);
        // TODO(177500482): Only support the *current* Input Method.
        List<InputMethodInfo> enabledInputMethods =
                LocalServices.getService(InputMethodManagerInternal.class)
                        .getEnabledInputMethodListAsUser(mUserId);
        mCallbacks.broadcast((callback, uid) -> {
            if ((int) uid == translationActivityUid) {
        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(res);
                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 ((int) uid == inputMethod.getServiceInfo().applicationInfo.uid) {
            if (callbackSourceUid == inputMethod.getServiceInfo().applicationInfo.uid) {
                isIme = true;
                break;
            }
        }
            // TODO(177500482): Invoke it for the application being translated too.

        if (!isIme) {
            return;
        }
        try {
                callback.sendResult(res);
            callback.sendResult(result);
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to invoke UiTranslationStateCallback: " + e);
        }
        });
    }

    public void registerUiTranslationStateCallback(IRemoteCallback callback, int sourceUid) {
    @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;
        }
    }
}