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

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

Merge changes from topic "b192331240-singleInvoke" into tm-dev am: ac9ae5d9...

Merge changes from topic "b192331240-singleInvoke" into tm-dev am: ac9ae5d9 am: 462af14e am: 519b8a32

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



Change-Id: I9f98c14b4cba3ce7bb99bd0e2af4f91a678b06e9
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 1e6cfc5d 519b8a32
Loading
Loading
Loading
Loading
+21 −7
Original line number Diff line number Diff line
@@ -25,14 +25,28 @@ import java.util.concurrent.Executor;
 * Callback for listening to UI Translation state changes. See {@link
 * UiTranslationManager#registerUiTranslationStateCallback(Executor, UiTranslationStateCallback)}.
 * <p>
 * Prior to Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}, callback methods
 * <em>without</em> {@code packageName} are invoked. Apps with minSdkVersion lower than {@link
 * android.os.Build.VERSION_CODES#TIRAMISU} <em>must</em> implement those methods if they want to
 * handle the events.
 * <p>
 * In Android version {@link android.os.Build.VERSION_CODES#TIRAMISU} and later, if both methods
 * with and without {@code packageName} are implemented (e.g., {@link #onFinished()} and {@link
 * #onFinished(String)}, only the one <em>with</em> {@code packageName} will be called.
 * Prior to Android version {@link android.os.Build.VERSION_CODES#TIRAMISU}:
 * <ul>
 *     <li>Callback methods <em>without</em> {@code packageName} are invoked. Apps with
 *     minSdkVersion lower than {@link android.os.Build.VERSION_CODES#TIRAMISU} <em>must</em>
 *     implement those methods if they want to handle the events.</li>
 *     <li>Callback methods for a particular event <em>may</em> be called multiple times
 *     consecutively, even when the translation state has not changed (e.g.,
 *     {@link #onStarted(ULocale, ULocale, String)} may be called multiple times even after
 *     translation has already started).</li>
 * </ul>
 * <p>
 * In Android version {@link android.os.Build.VERSION_CODES#TIRAMISU} and later:
 * <ul>
 *     <li>If both methods with and without {@code packageName} are implemented (e.g.,
 *     {@link #onFinished()} and {@link #onFinished(String)}, only the one <em>with</em> {@code
 *     packageName} will be called.</li>
 *     <li>Callback methods for a particular event will <em>not</em> be called multiple times
 *     consecutively. They will only be called when the translation state has actually changed
 *     (e.g., from "started" to "paused"). Note: "resumed" is not considered a separate state
 *     from "started", so {@link #onResumed(ULocale, ULocale, String)} will never be called after
 *     {@link #onStarted(ULocale, ULocale, String)}.<</li>
 * </ul>
 */
public interface UiTranslationStateCallback {

+185 −55
Original line number Diff line number Diff line
@@ -41,10 +41,10 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.service.translation.TranslationServiceInfo;
import android.util.ArrayMap;
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;
@@ -71,7 +71,8 @@ import java.lang.ref.WeakReference;
import java.util.List;

final class TranslationManagerServiceImpl extends
        AbstractPerUserSystemService<TranslationManagerServiceImpl, TranslationManagerService> {
        AbstractPerUserSystemService<TranslationManagerServiceImpl, TranslationManagerService>
        implements IBinder.DeathRecipient {

    private static final String TAG = "TranslationManagerServiceImpl";
    @SuppressLint("IsLoggableTagLength")
@@ -100,10 +101,10 @@ final class TranslationManagerServiceImpl extends
    private final ArraySet<IBinder> mWaitingFinishedCallbackActivities = new ArraySet<>();

    /**
     * Key is translated activity uid, value is the specification and state for the translation.
     * Key is translated activity token, value is the specification and state for the translation.
     */
    @GuardedBy("mLock")
    private final SparseArray<ActiveTranslation> mActiveTranslations = new SparseArray<>();
    private final ArrayMap<IBinder, ActiveTranslation> mActiveTranslations = new ArrayMap<>();

    protected TranslationManagerServiceImpl(
            @NonNull TranslationManagerService master,
@@ -190,25 +191,24 @@ final class TranslationManagerServiceImpl extends
        }
    }

    private int getActivityUidByComponentName(Context context, ComponentName componentName,
            int userId) {
        int translationActivityUid = -1;
    private int getAppUidByComponentName(Context context, ComponentName componentName, int userId) {
        int translatedAppUid = -1;
        try {
            if (componentName != null) {
                translationActivityUid = context.getPackageManager().getApplicationInfoAsUser(
                translatedAppUid = context.getPackageManager().getApplicationInfoAsUser(
                        componentName.getPackageName(), 0, userId).uid;
            }
        } catch (PackageManager.NameNotFoundException e) {
            Slog.d(TAG, "Cannot find packageManager for" + componentName);
        }
        return translationActivityUid;
        return translatedAppUid;
    }

    @GuardedBy("mLock")
    public void onTranslationFinishedLocked(boolean activityDestroyed, IBinder token,
            ComponentName componentName) {
        final int translationActivityUid =
                getActivityUidByComponentName(getContext(), componentName, getUserId());
        final int translatedAppUid =
                getAppUidByComponentName(getContext(), componentName, getUserId());
        final String packageName = componentName.getPackageName();
        if (activityDestroyed) {
            // In the Activity destroy case, we only calls onTranslationFinished() in
@@ -216,13 +216,13 @@ final class TranslationManagerServiceImpl extends
            // should remove the waiting callback to avoid callback twice.
            invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
                    /* sourceSpec= */ null, /* targetSpec= */ null,
                    packageName, translationActivityUid);
                    packageName, translatedAppUid);
            mWaitingFinishedCallbackActivities.remove(token);
        } else {
            if (mWaitingFinishedCallbackActivities.contains(token)) {
                invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
                        /* sourceSpec= */ null, /* targetSpec= */ null,
                        packageName, translationActivityUid);
                        packageName, translatedAppUid);
                mWaitingFinishedCallbackActivities.remove(token);
            }
        }
@@ -259,51 +259,164 @@ final class TranslationManagerServiceImpl extends
        }

        ComponentName componentName = mActivityTaskManagerInternal.getActivityName(activityToken);
        int translationActivityUid =
                getActivityUidByComponentName(getContext(), componentName, getUserId());
        int translatedAppUid =
                getAppUidByComponentName(getContext(), componentName, getUserId());
        String packageName = componentName.getPackageName();
        if (state != STATE_UI_TRANSLATION_FINISHED) {
            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");
            }
        }

        invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, activityToken,
                translatedAppUid);
        updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, activityToken,
                translatedAppUid);
    }

    @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.
    private void updateActiveTranslationsLocked(int state, TranslationSpec sourceSpec,
            TranslationSpec targetSpec, String packageName, IBinder activityToken,
            int translatedAppUid) {
        // We keep track of active translations and their state so that we can:
        // 1. Trigger callbacks that are registered after translation has started.
        //    See registerUiTranslationStateCallbackLocked().
        // 2. NOT trigger callbacks when the state didn't change.
        //    See invokeCallbacksIfNecessaryLocked().
        ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
        switch (state) {
            case STATE_UI_TRANSLATION_STARTED: {
                ActiveTranslation activeTranslation = new ActiveTranslation(sourceSpec,
                        targetSpec, packageName);
                mActiveTranslations.put(translationActivityUid, activeTranslation);
                if (activeTranslation == null) {
                    try {
                        activityToken.linkToDeath(this, /* flags= */ 0);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "Failed to call linkToDeath for translated app with uid="
                                + translatedAppUid + "; activity is already dead", e);

                        // Apps with registered callbacks were just notified that translation
                        // started. We should let them know translation is finished too.
                        invokeCallbacks(STATE_UI_TRANSLATION_FINISHED, sourceSpec, targetSpec,
                                packageName, translatedAppUid);
                        return;
                    }
                    mActiveTranslations.put(activityToken,
                            new ActiveTranslation(sourceSpec, targetSpec, translatedAppUid,
                                    packageName));
                }
                break;
            }
            case STATE_UI_TRANSLATION_PAUSED:

            case STATE_UI_TRANSLATION_PAUSED: {
                if (activeTranslation != null) {
                    activeTranslation.isPaused = true;
                }
                break;
            }

            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
                    activeTranslation.isPaused = false;
                }
                break;
            }

            case STATE_UI_TRANSLATION_FINISHED: {
                if (activeTranslation != null) {
                    mActiveTranslations.remove(activityToken);
                }
                break;
            }
        }

        if (DEBUG) {
            Slog.d(TAG,
                    "Updating to translation state=" + state + " for app with uid="
                            + translatedAppUid + " packageName=" + packageName);
        }
    }

    @GuardedBy("mLock")
    private void invokeCallbacksIfNecessaryLocked(int state, TranslationSpec sourceSpec,
            TranslationSpec targetSpec, String packageName, IBinder activityToken,
            int translatedAppUid) {
        boolean shouldInvokeCallbacks = true;
        int stateForCallbackInvocation = state;

        ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
        if (activeTranslation == null) {
            if (state != STATE_UI_TRANSLATION_STARTED) {
                shouldInvokeCallbacks = false;
                Slog.w(TAG,
                        "Updating to translation state=" + state + " for app with uid="
                                + translatedAppUid + " packageName=" + packageName
                                + " but no active translation was found for it");
            }
        } else {
            switch (state) {
                case STATE_UI_TRANSLATION_STARTED: {
                    boolean specsAreIdentical = activeTranslation.sourceSpec.getLocale().equals(
                            sourceSpec.getLocale())
                            && activeTranslation.targetSpec.getLocale().equals(
                            targetSpec.getLocale());
                    if (specsAreIdentical) {
                        if (activeTranslation.isPaused) {
                            // Ideally UiTranslationManager.resumeTranslation() should be first
                            // used to resume translation, but for the purposes of invoking the
                            // callback, we want to call onResumed() instead of onStarted(). This
                            // way there can only be one call to onStarted() for the lifetime of
                            // a translated activity and this will simplify the number of states
                            // apps have to handle.
                            stateForCallbackInvocation = STATE_UI_TRANSLATION_RESUMED;
                        } else {
                            // Don't invoke callbacks if the state or specs didn't change. For a
                            // given activity, startTranslation() will be called every time there
                            // are new views to be translated, but we don't need to repeatedly
                            // notify apps about it.
                            shouldInvokeCallbacks = false;
                        }
                    }
                    break;
                }

                case STATE_UI_TRANSLATION_PAUSED: {
                    if (activeTranslation.isPaused) {
                        // Don't invoke callbacks if the state didn't change.
                        shouldInvokeCallbacks = false;
                    }
                    break;
                }

                case STATE_UI_TRANSLATION_RESUMED: {
                    if (!activeTranslation.isPaused) {
                        // Don't invoke callbacks if the state didn't change. Either
                        // resumeTranslation() was called consecutive times, or right after
                        // startTranslation(). The latter case shouldn't happen normally, so we
                        // don't want apps to have to handle that particular transition.
                        shouldInvokeCallbacks = false;
                    }
                    break;
                }

                case STATE_UI_TRANSLATION_FINISHED: {
                    // Note: Here finishTranslation() was called but we don't want to invoke
                    // onFinished() on the callbacks. They will be invoked when
                    // UiTranslationManager.onTranslationFinished() is called (see
                    // onTranslationFinishedLocked()).
                    shouldInvokeCallbacks = false;
                    break;
                }
            }
        }

        if (DEBUG) {
            Slog.d(TAG,
                    (shouldInvokeCallbacks ? "" : "NOT ")
                            + "Invoking callbacks for translation state="
                            + stateForCallbackInvocation + " for app with uid=" + translatedAppUid
                            + " packageName=" + packageName);
        }

        if (shouldInvokeCallbacks) {
            invokeCallbacks(stateForCallbackInvocation, sourceSpec, targetSpec, packageName,
                    translatedAppUid);
        }
    }

    @GuardedBy("mLock")
    public void dumpLocked(String prefix, FileDescriptor fd, PrintWriter pw) {
        if (mLastActivityTokens != null) {
@@ -343,15 +456,14 @@ final class TranslationManagerServiceImpl extends

    private void invokeCallbacks(
            int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName,
            int translationActivityUid) {
            int translatedAppUid) {
        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);
            invokeCallback((int) uid, translatedAppUid, callback, result, enabledInputMethods);
        });
    }

@@ -374,9 +486,9 @@ final class TranslationManagerServiceImpl extends
    }

    private void invokeCallback(
            int callbackSourceUid, int translationActivityUid, IRemoteCallback callback,
            int callbackSourceUid, int translatedAppUid, IRemoteCallback callback,
            Bundle result, List<InputMethodInfo> enabledInputMethods) {
        if (callbackSourceUid == translationActivityUid) {
        if (callbackSourceUid == translatedAppUid) {
            // Invoke callback for the application being translated.
            try {
                callback.sendResult(result);
@@ -417,29 +529,26 @@ final class TranslationManagerServiceImpl extends
        // 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;
            }
            int translatedAppUid = activeTranslation.translatedAppUid;
            String packageName = activeTranslation.packageName;
            if (DEBUG) {
                Slog.d(TAG, "Triggering callback for sourceUid=" + sourceUid
                        + " for translated activity with uid=" + activeTranslationUid
                        + " for translated app with uid=" + translatedAppUid
                        + "packageName=" + packageName + " isPaused=" + activeTranslation.isPaused);
            }

            Bundle startedResult = createResultForCallback(STATE_UI_TRANSLATION_STARTED,
                    activeTranslation.sourceSpec, activeTranslation.targetSpec,
                    packageName);
            invokeCallback(sourceUid, activeTranslationUid, callback, startedResult,
            invokeCallback(sourceUid, translatedAppUid, 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,
                invokeCallback(sourceUid, translatedAppUid, callback, pausedResult,
                        enabledInputMethods);
            }
        }
@@ -492,13 +601,34 @@ final class TranslationManagerServiceImpl extends
        public final TranslationSpec sourceSpec;
        public final TranslationSpec targetSpec;
        public final String packageName;
        public final int translatedAppUid;
        public boolean isPaused = false;

        private ActiveTranslation(TranslationSpec sourceSpec, TranslationSpec targetSpec,
                String packageName) {
                int translatedAppUid, String packageName) {
            this.sourceSpec = sourceSpec;
            this.targetSpec = targetSpec;
            this.translatedAppUid = translatedAppUid;
            this.packageName = packageName;
        }
    }

    @Override
    public void binderDied() {
        // Don't need to implement this with binderDied(IBinder) implemented.
    }

    @Override
    public void binderDied(IBinder who) {
        synchronized (mLock) {
            mWaitingFinishedCallbackActivities.remove(who);
            ActiveTranslation activeTranslation = mActiveTranslations.remove(who);
            if (activeTranslation != null) {
                // Let apps with registered callbacks know about the activity's death.
                invokeCallbacks(STATE_UI_TRANSLATION_FINISHED, activeTranslation.sourceSpec,
                        activeTranslation.targetSpec, activeTranslation.packageName,
                        activeTranslation.translatedAppUid);
            }
        }
    }
}