All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.view.translation.Translator Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2020 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 static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
import static android.view.translation.TranslationManager.SYNC_CALLS_TIMEOUT_MS;
import static android.view.translation.UiTranslationController.DEBUG;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.translation.ITranslationCallback;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;

import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * The {@link Translator} for translation, defined by a {@link TranslationContext}.
 */
@SuppressLint("NotCloseable")
public class Translator {

    private static final String TAG = "Translator";

    // TODO: make this configurable and cross the Translation component
    private static boolean sDEBUG = false;

    private final Object mLock = new Object();

    private int mId;

    @NonNull
    private final Context mContext;

    @NonNull
    private final TranslationContext mTranslationContext;

    @NonNull
    private final TranslationManager mManager;

    @NonNull
    private final Handler mHandler;

    /**
     * Interface to the system_server binder object.
     */
    private ITranslationManager mSystemServerBinder;

    /**
     * Direct interface to the TranslationService binder object.
     */
    @Nullable
    private ITranslationDirectManager mDirectServiceBinder;

    @NonNull
    private final ServiceBinderReceiver mServiceBinderReceiver;

    @GuardedBy("mLock")
    private boolean mDestroyed;

    /**
     * Name of the {@link IResultReceiver} extra used to pass the binder interface to Translator.
     * @hide
     */
    public static final String EXTRA_SERVICE_BINDER = "binder";
    /**
     * Name of the extra used to pass the session id to Translator.
     * @hide
     */
    public static final String EXTRA_SESSION_ID = "sessionId";

    static class ServiceBinderReceiver extends IResultReceiver.Stub {
        // TODO: refactor how translator is instantiated after removing deprecated createTranslator.
        private final Translator mTranslator;
        private final CountDownLatch mLatch = new CountDownLatch(1);
        private int mSessionId;

        private Consumer mCallback;

        ServiceBinderReceiver(Translator translator, Consumer callback) {
            mTranslator = translator;
            mCallback = callback;
        }

        ServiceBinderReceiver(Translator translator) {
            mTranslator = translator;
        }

        int getSessionStateResult() throws TimeoutException {
            try {
                if (!mLatch.await(SYNC_CALLS_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                    throw new TimeoutException(
                            "Session not created in " + SYNC_CALLS_TIMEOUT_MS + "ms");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new TimeoutException("Session not created because interrupted");
            }
            return mSessionId;
        }

        @Override
        public void send(int resultCode, Bundle resultData) {
            if (resultCode == STATUS_SYNC_CALL_FAIL) {
                mLatch.countDown();
                if (mCallback != null) {
                    mCallback.accept(null);
                }
                return;
            }
            final IBinder binder;
            if (resultData != null) {
                mSessionId = resultData.getInt(EXTRA_SESSION_ID);
                binder = resultData.getBinder(EXTRA_SERVICE_BINDER);
                if (binder == null) {
                    Log.wtf(TAG, "No " + EXTRA_SERVICE_BINDER + " extra result");
                    return;
                }
            } else {
                binder = null;
            }
            mTranslator.setServiceBinder(binder);
            mLatch.countDown();
            if (mCallback != null) {
                mCallback.accept(mTranslator);
            }
        }

        // TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor public
        //  and use it.
        static final class TimeoutException extends Exception {
            private TimeoutException(String msg) {
                super(msg);
            }
        }
    }

    /**
     * Create the Translator.
     *
     * @hide
     */
    public Translator(@NonNull Context context,
            @NonNull TranslationContext translationContext, int sessionId,
            @NonNull TranslationManager translationManager, @NonNull Handler handler,
            @Nullable ITranslationManager systemServerBinder,
            @NonNull Consumer callback) {
        mContext = context;
        mTranslationContext = translationContext;
        mId = sessionId;
        mManager = translationManager;
        mHandler = handler;
        mSystemServerBinder = systemServerBinder;
        mServiceBinderReceiver = new ServiceBinderReceiver(this, callback);

        try {
            mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
                    mServiceBinderReceiver, mContext.getUserId());
        } catch (RemoteException e) {
            Log.w(TAG, "RemoteException calling startSession(): " + e);
        }
    }

    /**
     * Create the Translator.
     *
     * @hide
     */
    public Translator(@NonNull Context context,
            @NonNull TranslationContext translationContext, int sessionId,
            @NonNull TranslationManager translationManager, @NonNull Handler handler,
            @Nullable ITranslationManager systemServerBinder) {
        mContext = context;
        mTranslationContext = translationContext;
        mId = sessionId;
        mManager = translationManager;
        mHandler = handler;
        mSystemServerBinder = systemServerBinder;
        mServiceBinderReceiver = new ServiceBinderReceiver(this);
    }

    /**
     * Starts this Translator session.
     */
    void start() {
        try {
            mSystemServerBinder.onSessionCreated(mTranslationContext, mId,
                    mServiceBinderReceiver, mContext.getUserId());
        } catch (RemoteException e) {
            Log.w(TAG, "RemoteException calling startSession(): " + e);
        }
    }

    /**
     * Wait this Translator session created.
     *
     * @return {@code true} if the session is created successfully.
     */
    boolean isSessionCreated() throws ServiceBinderReceiver.TimeoutException {
        int receivedId = mServiceBinderReceiver.getSessionStateResult();
        return receivedId > 0;
    }

    private int getNextRequestId() {
        // Get from manager to keep the request id unique to different Translators
        return mManager.getAvailableRequestId().getAndIncrement();
    }

    private void setServiceBinder(@Nullable IBinder binder) {
        synchronized (mLock) {
            if (mDirectServiceBinder != null) {
                return;
            }
            if (binder != null) {
                mDirectServiceBinder = ITranslationDirectManager.Stub.asInterface(binder);
            }
        }
    }

    /** @hide */
    public TranslationContext getTranslationContext() {
        return mTranslationContext;
    }

    /** @hide */
    public int getTranslatorId() {
        return mId;
    }

    /** @hide */
    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
        pw.print(prefix); pw.print("translationContext: "); pw.println(mTranslationContext);
    }

    /**
     * Requests a translation for the provided {@link TranslationRequest} using the Translator's
     * source spec and destination spec.
     *
     * @param request {@link TranslationRequest} request to be translate.
     *
     * @throws IllegalStateException if this Translator session was destroyed when called.
     *
     * @removed use {@link #translate(TranslationRequest, CancellationSignal,
     *             Executor, Consumer)} instead.
     */
    @Deprecated
    @Nullable
    public void translate(@NonNull TranslationRequest request,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer callback) {
        Objects.requireNonNull(request, "Translation request cannot be null");
        Objects.requireNonNull(executor, "Executor cannot be null");
        Objects.requireNonNull(callback, "Callback cannot be null");

        if (isDestroyed()) {
            // TODO(b/176464808): Disallow multiple Translator now, it will throw
            //  IllegalStateException. Need to discuss if we can allow multiple Translators.
            throw new IllegalStateException(
                    "This translator has been destroyed");
        }

        final ITranslationCallback responseCallback =
                new TranslationResponseCallbackImpl(callback, executor);
        try {
            mDirectServiceBinder.onTranslationRequest(request, mId,
                    CancellationSignal.createTransport(), responseCallback);
        } catch (RemoteException e) {
            Log.w(TAG, "RemoteException calling requestTranslate(): " + e);
        }
    }

    /**
     * Requests a translation for the provided {@link TranslationRequest} using the Translator's
     * source spec and destination spec.
     *
     * @param request {@link TranslationRequest} request to be translate.
     * @param cancellationSignal signal to cancel the operation in progress.
     * @param executor Executor to run callback operations
     * @param callback {@link Consumer} to receive the translation response. Multiple responses may
     *                 be received if {@link TranslationRequest#FLAG_PARTIAL_RESPONSES} is set.
     *
     * @throws IllegalStateException if this Translator session was destroyed when called.
     */
    @Nullable
    public void translate(@NonNull TranslationRequest request,
            @Nullable CancellationSignal cancellationSignal,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer callback) {
        Objects.requireNonNull(request, "Translation request cannot be null");
        Objects.requireNonNull(executor, "Executor cannot be null");
        Objects.requireNonNull(callback, "Callback cannot be null");

        if (isDestroyed()) {
            // TODO(b/176464808): Disallow multiple Translator now, it will throw
            //  IllegalStateException. Need to discuss if we can allow multiple Translators.
            throw new IllegalStateException(
                    "This translator has been destroyed");
        }

        ICancellationSignal transport = null;
        if (cancellationSignal != null) {
            transport = CancellationSignal.createTransport();
            cancellationSignal.setRemote(transport);
        }
        final ITranslationCallback responseCallback =
                new TranslationResponseCallbackImpl(callback, executor);

        try {
            mDirectServiceBinder.onTranslationRequest(request, mId, transport,
                    responseCallback);
        } catch (RemoteException e) {
            Log.w(TAG, "RemoteException calling requestTranslate(): " + e);
        }
    }

    /**
     * Destroy this Translator.
     */
    public void destroy() {
        synchronized (mLock) {
            if (mDestroyed) {
                return;
            }
            mDestroyed = true;
            try {
                mDirectServiceBinder.onFinishTranslationSession(mId);
            } catch (RemoteException e) {
                Log.w(TAG, "RemoteException calling onSessionFinished");
            }
            mDirectServiceBinder = null;
            mManager.removeTranslator(mId);
        }
    }

    /**
     * Returns whether or not this Translator has been destroyed.
     *
     * @see #destroy()
     */
    public boolean isDestroyed() {
        synchronized (mLock) {
            return mDestroyed;
        }
    }

    // TODO: add methods for UI-toolkit case.
    /** @hide */
    public void requestUiTranslate(@NonNull TranslationRequest request,
            @NonNull Executor executor,
            @NonNull Consumer callback) {
        if (mDirectServiceBinder == null) {
            Log.wtf(TAG, "Translator created without proper initialization.");
            return;
        }
        final ITranslationCallback translationCallback =
                new TranslationResponseCallbackImpl(callback, executor);
        try {
            mDirectServiceBinder.onTranslationRequest(request, mId,
                    CancellationSignal.createTransport(), translationCallback);
        } catch (RemoteException e) {
            Log.w(TAG, "RemoteException calling flushRequest");
        }
    }

    private static class TranslationResponseCallbackImpl extends ITranslationCallback.Stub {

        private final Consumer mCallback;
        private final Executor mExecutor;

        TranslationResponseCallbackImpl(Consumer callback, Executor executor) {
            mCallback = callback;
            mExecutor = executor;
        }

        @Override
        public void onTranslationResponse(TranslationResponse response) throws RemoteException {
            if (DEBUG) {
                Log.i(TAG, "onTranslationResponse called.");
            }
            final Runnable runnable =
                    () -> mCallback.accept(response);
            final long token = Binder.clearCallingIdentity();
            try {
                mExecutor.execute(runnable);
            } finally {
                restoreCallingIdentity(token);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy