Initial layout of translation related APIs.

The initial translation related APIs. The APIs and the implementation
will be revised in the follow up changes. The service register will
on the next changes.

Bug: 173243538
Bug: 176208267
Test: manual verification (build)

Change-Id: Ib7b39e1b548bc6663a81482fa335632e8da18d6a
diff --git a/core/api/current.txt b/core/api/current.txt
index 2464dda..419c18a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12325,6 +12325,7 @@
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
+    field public static final String FEATURE_TRANSLATION = "android.software.translation";
     field public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
     field public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
     field public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
@@ -51524,6 +51525,66 @@
 
 }
 
+package android.view.translation {
+
+  public final class TranslationManager {
+    method @Nullable @WorkerThread public android.view.translation.Translator createTranslator(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec);
+    method @NonNull @WorkerThread public java.util.List<java.lang.String> getSupportedLocales();
+  }
+
+  public final class TranslationRequest implements android.os.Parcelable {
+    ctor public TranslationRequest(@Nullable CharSequence);
+    method public int describeContents();
+    method @Nullable public android.view.autofill.AutofillId getAutofillId();
+    method @Nullable public CharSequence getTranslationText();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationRequest> CREATOR;
+  }
+
+  public static final class TranslationRequest.Builder {
+    ctor public TranslationRequest.Builder();
+    method @NonNull public android.view.translation.TranslationRequest build();
+    method @NonNull public android.view.translation.TranslationRequest.Builder setAutofillId(@NonNull android.view.autofill.AutofillId);
+    method @NonNull public android.view.translation.TranslationRequest.Builder setTranslationText(@NonNull CharSequence);
+  }
+
+  public final class TranslationResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getTranslationStatus();
+    method @NonNull public java.util.List<android.view.translation.TranslationRequest> getTranslations();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationResponse> CREATOR;
+    field public static final int TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE = 2; // 0x2
+    field public static final int TRANSLATION_STATUS_SUCCESS = 0; // 0x0
+    field public static final int TRANSLATION_STATUS_UNKNOWN_ERROR = 1; // 0x1
+  }
+
+  public static final class TranslationResponse.Builder {
+    ctor public TranslationResponse.Builder(int);
+    method @NonNull public android.view.translation.TranslationResponse.Builder addTranslations(@NonNull android.view.translation.TranslationRequest);
+    method @NonNull public android.view.translation.TranslationResponse build();
+    method @NonNull public android.view.translation.TranslationResponse.Builder setTranslationStatus(int);
+    method @NonNull public android.view.translation.TranslationResponse.Builder setTranslations(@NonNull java.util.List<android.view.translation.TranslationRequest>);
+  }
+
+  public final class TranslationSpec implements android.os.Parcelable {
+    ctor public TranslationSpec(@NonNull String, int);
+    method public int describeContents();
+    method public int getDataFormat();
+    method @NonNull public String getLanguage();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.translation.TranslationSpec> CREATOR;
+    field public static final int DATA_FORMAT_TEXT = 1; // 0x1
+  }
+
+  public class Translator {
+    method public void destroy();
+    method public boolean isDestroyed();
+    method @Nullable @WorkerThread public android.view.translation.TranslationResponse translate(@NonNull android.view.translation.TranslationRequest);
+  }
+
+}
+
 package android.webkit {
 
   public abstract class ClientCertRequest {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8d748e6..7bbfe9b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -52,6 +52,7 @@
     field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
     field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
     field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
+    field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
     field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
     field public static final String BRICK = "android.permission.BRICK";
@@ -1935,6 +1936,7 @@
     field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
     field public static final String TETHERING_SERVICE = "tethering";
+    field public static final String TRANSLATION_MANAGER_SERVICE = "transformer";
     field public static final String VR_SERVICE = "vrmanager";
     field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
@@ -9795,6 +9797,48 @@
 
 }
 
+package android.service.translation {
+
+  public final class TranslationRequest implements android.os.Parcelable {
+    ctor public TranslationRequest(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.translation.TranslationRequest>);
+    method public int describeContents();
+    method @NonNull public android.view.translation.TranslationSpec getDestSpec();
+    method public int getRequestId();
+    method @NonNull public android.view.translation.TranslationSpec getSourceSpec();
+    method @NonNull public java.util.List<android.view.translation.TranslationRequest> getTranslationRequests();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.translation.TranslationRequest> CREATOR;
+  }
+
+  public static final class TranslationRequest.Builder {
+    ctor public TranslationRequest.Builder(int, @NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.translation.TranslationRequest>);
+    method @NonNull public android.service.translation.TranslationRequest.Builder addTranslationRequests(@NonNull android.view.translation.TranslationRequest);
+    method @NonNull public android.service.translation.TranslationRequest build();
+    method @NonNull public android.service.translation.TranslationRequest.Builder setDestSpec(@NonNull android.view.translation.TranslationSpec);
+    method @NonNull public android.service.translation.TranslationRequest.Builder setRequestId(int);
+    method @NonNull public android.service.translation.TranslationRequest.Builder setSourceSpec(@NonNull android.view.translation.TranslationSpec);
+    method @NonNull public android.service.translation.TranslationRequest.Builder setTranslationRequests(@NonNull java.util.List<android.view.translation.TranslationRequest>);
+  }
+
+  public abstract class TranslationService extends android.app.Service {
+    ctor public TranslationService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public void onConnected();
+    method public abstract void onCreateTranslationSession(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, int);
+    method public void onDisconnected();
+    method public abstract void onFinishTranslationSession(int);
+    method public abstract void onTranslationRequest(@NonNull android.service.translation.TranslationRequest, int, @NonNull android.os.CancellationSignal, @NonNull android.service.translation.TranslationService.OnTranslationResultCallback);
+    field public static final String SERVICE_INTERFACE = "android.service.translation.TranslationService";
+    field public static final String SERVICE_META_DATA = "android.translation_service";
+  }
+
+  public static interface TranslationService.OnTranslationResultCallback {
+    method public void onError();
+    method public void onTranslationSuccess(@NonNull android.view.translation.TranslationResponse);
+  }
+
+}
+
 package android.service.trust {
 
   public class TrustAgentService extends android.app.Service {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index abe7fda..d9f4e41 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4508,6 +4508,17 @@
     public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
 
     /**
+     * Official published name of the translation service.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    // TODO(b/176208267): change it back to translation before S release.
+    @SystemApi
+    @SuppressLint("ServiceName")
+    public static final String TRANSLATION_MANAGER_SERVICE = "transformer";
+
+    /**
      * Used for getting content selections and classifications for task snapshots.
      *
      * @hide
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e074eab..17c4d25 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3406,6 +3406,14 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device supports translation of text-to-text in multiple languages via integration with
+     * the system {@link android.service.translation.TranslationService translation provider}.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TRANSLATION = "android.software.translation";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device implements headtracking suitable for a VR device.
      */
     @SdkConstant(SdkConstantType.FEATURE)
diff --git a/core/java/android/service/translation/ITranslationCallback.aidl b/core/java/android/service/translation/ITranslationCallback.aidl
new file mode 100644
index 0000000..333cb57
--- /dev/null
+++ b/core/java/android/service/translation/ITranslationCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.service.translation;
+
+import android.view.translation.TranslationResponse;
+
+/**
+ * Interface to receive the result of a {@code TranslationRequest}.
+ *
+ * @hide
+ */
+oneway interface ITranslationCallback {
+    void onTranslationComplete(in TranslationResponse translationResponse);
+    void onError();
+}
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
new file mode 100644
index 0000000..6d6f278
--- /dev/null
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.service.translation;
+
+import android.service.translation.TranslationRequest;
+import android.service.translation.ITranslationCallback;
+import android.view.translation.TranslationSpec;
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * System-wide on-device translation service.
+ *
+ * <p>Services requests to translate text between different languages. The primary use case for this
+ * service is automatic translation of text and web views, when the auto Translate feature is
+ * enabled.
+ *
+ * @hide
+ */
+oneway interface ITranslationService {
+    void onConnected();
+    void onDisconnected();
+    void onCreateTranslationSession(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+         int sessionId, in IResultReceiver receiver);
+}
diff --git a/core/java/android/service/translation/OWNERS b/core/java/android/service/translation/OWNERS
new file mode 100644
index 0000000..a1e663a
--- /dev/null
+++ b/core/java/android/service/translation/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 994311
+
+adamhe@google.com
+augale@google.com
+joannechung@google.com
+lpeter@google.com
+svetoslavganov@google.com
+tymtsai@google.com
diff --git a/core/java/android/service/translation/OnTranslationResultCallbackWrapper.java b/core/java/android/service/translation/OnTranslationResultCallbackWrapper.java
new file mode 100644
index 0000000..345c69c
--- /dev/null
+++ b/core/java/android/service/translation/OnTranslationResultCallbackWrapper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.service.translation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.translation.TranslationResponse;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Callback to receive the {@link TranslationResponse} on successful translation.
+ *
+ * @hide
+ */
+final class OnTranslationResultCallbackWrapper implements
+        TranslationService.OnTranslationResultCallback {
+
+    private static final String TAG = "OnTranslationResultCallback";
+
+    private final @NonNull ITranslationCallback mCallback;
+
+    private AtomicBoolean mCalled;
+
+    /**
+     * @hide
+     */
+    public OnTranslationResultCallbackWrapper(@NonNull ITranslationCallback callback) {
+        mCallback = Objects.requireNonNull(callback);
+        mCalled = new AtomicBoolean();
+    }
+
+    @Override
+    public void onTranslationSuccess(@Nullable TranslationResponse response) {
+        assertNotCalled();
+        if (mCalled.getAndSet(true)) {
+            throw new IllegalStateException("Already called");
+        }
+
+        try {
+            mCallback.onTranslationComplete(response);
+        } catch (RemoteException e) {
+            if (e instanceof DeadObjectException) {
+                Log.w(TAG, "Process is dead, ignore.");
+                return;
+            }
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void onError() {
+        assertNotCalled();
+        if (mCalled.getAndSet(true)) {
+            throw new IllegalStateException("Already called");
+        }
+
+        try {
+            mCallback.onError();
+        } catch (RemoteException e) {
+            if (e instanceof DeadObjectException) {
+                Log.w(TAG, "Process is dead, ignore.");
+                return;
+            }
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    private void assertNotCalled() {
+        if (mCalled.get()) {
+            throw new IllegalStateException("Already called");
+        }
+    }
+}
diff --git a/core/java/android/service/translation/TranslationRequest.aidl b/core/java/android/service/translation/TranslationRequest.aidl
new file mode 100644
index 0000000..9a2d415
--- /dev/null
+++ b/core/java/android/service/translation/TranslationRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.service.translation;
+
+parcelable TranslationRequest;
diff --git a/core/java/android/service/translation/TranslationRequest.java b/core/java/android/service/translation/TranslationRequest.java
new file mode 100644
index 0000000..b8afd70
--- /dev/null
+++ b/core/java/android/service/translation/TranslationRequest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.service.translation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.util.DataClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Internal translation request sent to the {@link android.service.translation.TranslationService}
+ * which contains the text to be translated.
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(genConstructor = true, genBuilder = true, genToString = true)
+public final class TranslationRequest implements Parcelable {
+
+    private final int mRequestId;
+    @NonNull
+    private final TranslationSpec mSourceSpec;
+    @NonNull
+    private final TranslationSpec mDestSpec;
+    @NonNull
+    private final List<android.view.translation.TranslationRequest> mTranslationRequests;
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/translation/TranslationRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    public TranslationRequest(
+            int requestId,
+            @NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec,
+            @NonNull List<android.view.translation.TranslationRequest> translationRequests) {
+        this.mRequestId = requestId;
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mDestSpec = destSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDestSpec);
+        this.mTranslationRequests = translationRequests;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationRequests);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public int getRequestId() {
+        return mRequestId;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getSourceSpec() {
+        return mSourceSpec;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull TranslationSpec getDestSpec() {
+        return mDestSpec;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull List<android.view.translation.TranslationRequest> getTranslationRequests() {
+        return mTranslationRequests;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationRequest { " +
+                "requestId = " + mRequestId + ", " +
+                "sourceSpec = " + mSourceSpec + ", " +
+                "destSpec = " + mDestSpec + ", " +
+                "translationRequests = " + mTranslationRequests +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mRequestId);
+        dest.writeTypedObject(mSourceSpec, flags);
+        dest.writeTypedObject(mDestSpec, flags);
+        dest.writeParcelableList(mTranslationRequests, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationRequest(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int requestId = in.readInt();
+        TranslationSpec sourceSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        TranslationSpec destSpec = (TranslationSpec) in.readTypedObject(TranslationSpec.CREATOR);
+        List<android.view.translation.TranslationRequest> translationRequests = new ArrayList<>();
+        in.readParcelableList(translationRequests, android.view.translation.TranslationRequest.class.getClassLoader());
+
+        this.mRequestId = requestId;
+        this.mSourceSpec = sourceSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSourceSpec);
+        this.mDestSpec = destSpec;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mDestSpec);
+        this.mTranslationRequests = translationRequests;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslationRequests);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationRequest> CREATOR
+            = new Parcelable.Creator<TranslationRequest>() {
+        @Override
+        public TranslationRequest[] newArray(int size) {
+            return new TranslationRequest[size];
+        }
+
+        @Override
+        public TranslationRequest createFromParcel(@NonNull Parcel in) {
+            return new TranslationRequest(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationRequest}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private int mRequestId;
+        private @NonNull TranslationSpec mSourceSpec;
+        private @NonNull TranslationSpec mDestSpec;
+        private @NonNull List<android.view.translation.TranslationRequest> mTranslationRequests;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder(
+                int requestId,
+                @NonNull TranslationSpec sourceSpec,
+                @NonNull TranslationSpec destSpec,
+                @NonNull List<android.view.translation.TranslationRequest> translationRequests) {
+            mRequestId = requestId;
+            mSourceSpec = sourceSpec;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mSourceSpec);
+            mDestSpec = destSpec;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mDestSpec);
+            mTranslationRequests = translationRequests;
+            com.android.internal.util.AnnotationValidations.validate(
+                    NonNull.class, null, mTranslationRequests);
+        }
+
+        @DataClass.Generated.Member
+        public @NonNull Builder setRequestId(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mRequestId = value;
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @NonNull Builder setSourceSpec(@NonNull TranslationSpec value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mSourceSpec = value;
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @NonNull Builder setDestSpec(@NonNull TranslationSpec value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mDestSpec = value;
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslationRequests(@NonNull List<android.view.translation.TranslationRequest> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x8;
+            mTranslationRequests = value;
+            return this;
+        }
+
+        /** @see #setTranslationRequests */
+        @DataClass.Generated.Member
+        public @NonNull Builder addTranslationRequests(@NonNull android.view.translation.TranslationRequest value) {
+            // You can refine this method's name by providing item's singular name, e.g.:
+            // @DataClass.PluralOf("item")) mItems = ...
+
+            if (mTranslationRequests == null) setTranslationRequests(new ArrayList<>());
+            mTranslationRequests.add(value);
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationRequest build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x10; // Mark builder used
+
+            TranslationRequest o = new TranslationRequest(
+                    mRequestId,
+                    mSourceSpec,
+                    mDestSpec,
+                    mTranslationRequests);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x10) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1609966181888L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/service/translation/TranslationRequest.java",
+            inputSignatures = "private final  int mRequestId\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mDestSpec\nprivate final @android.annotation.NonNull java.util.List<android.view.translation.TranslationRequest> mTranslationRequests\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=true, genBuilder=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
new file mode 100644
index 0000000..b028807
--- /dev/null
+++ b/core/java/android/service/translation/TranslationService.java
@@ -0,0 +1,261 @@
+/*
+ * 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.service.translation;
+
+import static android.view.translation.Translator.EXTRA_SERVICE_BINDER;
+import static android.view.translation.Translator.EXTRA_SESSION_ID;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.translation.ITranslationDirectManager;
+import android.view.translation.TranslationManager;
+import android.view.translation.TranslationResponse;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.SyncResultReceiver;
+
+/**
+ * Service for translating text.
+ * @hide
+ */
+@SystemApi
+public abstract class TranslationService extends Service {
+    private static final String TAG = "TranslationService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     *
+     * <p>To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_TRANSLATION_SERVICE} permission so
+     * that other applications can not abuse it.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.translation.TranslationService";
+
+    /**
+     * Name under which a TranslationService component publishes information about itself.
+     *
+     * <p>This meta-data should reference an XML resource containing a
+     * <code>&lt;{@link
+     * android.R.styleable#TranslationService translation-service}&gt;</code> tag.
+     *
+     * <p>Here's an example of how to use it on {@code AndroidManifest.xml}:
+     * TODO: fill in doc example (check CCService/AFService).
+     */
+    public static final String SERVICE_META_DATA = "android.translation_service";
+
+    private Handler mHandler;
+
+    /**
+     * Binder to receive calls from system server.
+     */
+    private final ITranslationService mInterface = new ITranslationService.Stub() {
+        @Override
+        public void onConnected() {
+            mHandler.sendMessage(obtainMessage(TranslationService::onConnected,
+                    TranslationService.this));
+        }
+
+        @Override
+        public void onDisconnected() {
+            mHandler.sendMessage(obtainMessage(TranslationService::onDisconnected,
+                    TranslationService.this));
+        }
+
+        @Override
+        public void onCreateTranslationSession(TranslationSpec sourceSpec, TranslationSpec destSpec,
+                int sessionId, IResultReceiver receiver) throws RemoteException {
+            mHandler.sendMessage(obtainMessage(TranslationService::handleOnCreateTranslationSession,
+                    TranslationService.this, sourceSpec, destSpec, sessionId, receiver));
+        }
+    };
+
+    /**
+     * Interface definition for a callback to be invoked when the translation is compleled.
+     */
+    public interface OnTranslationResultCallback {
+        /**
+         * Notifies the Android System that a translation request
+         * {@link TranslationService#onTranslationRequest(TranslationRequest, int,
+         * CancellationSignal, OnTranslationResultCallback)} was successfully fulfilled by the
+         * service.
+         *
+         * <p>This method should always be called, even if the service cannot fulfill the request
+         * (in which case it should be called with a TranslationResponse with
+         * {@link android.view.translation.TranslationResponse#TRANSLATION_STATUS_UNKNOWN_ERROR},
+         * or {@link android.view.translation.TranslationResponse
+         * #TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE}).
+         *
+         * @param response translation response for the provided request infos.
+         *
+         * @throws IllegalStateException if this method was already called.
+         */
+        void onTranslationSuccess(@NonNull TranslationResponse response);
+
+        /**
+         * TODO: implement javadoc
+         */
+        void onError();
+    }
+
+    /**
+     * Binder that receives calls from the app.
+     */
+    private final ITranslationDirectManager mClientInterface =
+            new ITranslationDirectManager.Stub() {
+                // TODO: Implement cancellation signal
+                @NonNull
+                private final CancellationSignal mCancellationSignal = new CancellationSignal();
+
+                @Override
+                public void onTranslationRequest(TranslationRequest request, int sessionId,
+                        ITranslationCallback callback, IResultReceiver receiver)
+                        throws RemoteException {
+                    // TODO(b/176464808): Currently, the API is used for both sync and async case.
+                    // It may work now, but maybe two methods is more cleaner. To think how to
+                    // define the APIs for these two cases.
+                    final ITranslationCallback cb = callback != null
+                            ? callback
+                            : new ITranslationCallback.Stub() {
+                                @Override
+                                public void onTranslationComplete(
+                                        TranslationResponse translationResponse)
+                                        throws RemoteException {
+                                    receiver.send(0,
+                                            SyncResultReceiver.bundleFor(translationResponse));
+                                }
+
+                                @Override
+                                public void onError() throws RemoteException {
+                                    //TODO: implement default error callback
+                                }
+                            };
+                    // TODO(b/176464808): make it a private member of client
+                    final OnTranslationResultCallback translationResultCallback =
+                            new OnTranslationResultCallbackWrapper(cb);
+                    mHandler.sendMessage(obtainMessage(TranslationService::onTranslationRequest,
+                            TranslationService.this, request, sessionId, mCancellationSignal,
+                            translationResultCallback));
+                }
+
+                @Override
+                public void onFinishTranslationSession(int sessionId) throws RemoteException {
+                    mHandler.sendMessage(obtainMessage(
+                            TranslationService::onFinishTranslationSession,
+                            TranslationService.this, sessionId));
+                }
+            };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+        BaseBundle.setShouldDefuse(true);
+    }
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    /**
+     * Called when the Android system connects to service.
+     *
+     * <p>You should generally do initialization here rather than in {@link #onCreate}.
+     */
+    public void onConnected() {
+    }
+
+    /**
+     * Called when the Android system disconnects from the service.
+     *
+     * <p> At this point this service may no longer be an active {@link TranslationService}.
+     * It should not make calls on {@link TranslationManager} that requires the caller to be
+     * the current service.
+     */
+    public void onDisconnected() {
+    }
+
+    /**
+     * TODO: fill in javadoc.
+     *
+     * @param sourceSpec
+     * @param destSpec
+     * @param sessionId
+     */
+    // TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
+    // solution to make it's safe.
+    public abstract void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec, int sessionId);
+
+    /**
+     * TODO: fill in javadoc.
+     *
+     * @param sessionId
+     */
+    public abstract void onFinishTranslationSession(int sessionId);
+
+    /**
+     * TODO: fill in javadoc.
+     *
+     * @param request
+     * @param sessionId
+     * @param callback
+     * @param cancellationSignal
+     */
+    public abstract void onTranslationRequest(@NonNull TranslationRequest request, int sessionId,
+            @NonNull CancellationSignal cancellationSignal,
+            @NonNull OnTranslationResultCallback callback);
+
+    // TODO(b/176464808): Need to handle client dying case
+
+    // TODO(b/176464808): Need to handle the failure case. e.g. if the specs does not support
+
+    private void handleOnCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+        try {
+            final Bundle extras = new Bundle();
+            extras.putBinder(EXTRA_SERVICE_BINDER, mClientInterface.asBinder());
+            extras.putInt(EXTRA_SESSION_ID, sessionId);
+            resultReceiver.send(0, extras);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException sending client interface: " + e);
+        }
+        onCreateTranslationSession(sourceSpec, destSpec, sessionId);
+    }
+}
diff --git a/core/java/android/service/translation/TranslationServiceInfo.java b/core/java/android/service/translation/TranslationServiceInfo.java
new file mode 100644
index 0000000..18cc29d
--- /dev/null
+++ b/core/java/android/service/translation/TranslationServiceInfo.java
@@ -0,0 +1,173 @@
+/*
+ * 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.service.translation;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * {@link ServiceInfo} and meta-data about an {@link TranslationService}.
+ *
+ * @hide
+ */
+public final class TranslationServiceInfo {
+
+    private static final String TAG = "TranslationServiceInfo";
+    private static final String XML_TAG_SERVICE = "translation-service";
+
+    @NonNull
+    private final ServiceInfo mServiceInfo;
+
+    @Nullable
+    private final String mSettingsActivity;
+
+    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, boolean isTemp,
+            @UserIdInt int userId) throws PackageManager.NameNotFoundException {
+        int flags = PackageManager.GET_META_DATA;
+        if (!isTemp) {
+            flags |= PackageManager.MATCH_SYSTEM_ONLY;
+        }
+
+        ServiceInfo si = null;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId);
+        } catch (RemoteException e) {
+        }
+        if (si == null) {
+            throw new NameNotFoundException("Could not get serviceInfo for "
+                    + (isTemp ? " (temp)" : "(default system)")
+                    + " " + comp.flattenToShortString());
+        }
+        return si;
+    }
+
+    @NonNull
+    public ServiceInfo getServiceInfo() {
+        return mServiceInfo;
+    }
+
+    @Nullable
+    public String getSettingsActivity() {
+        return mSettingsActivity;
+    }
+
+    public TranslationServiceInfo(@NonNull Context context, @NonNull ComponentName comp,
+            boolean isTemporaryService, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
+        this(context, getServiceInfoOrThrow(comp, isTemporaryService, userId));
+    }
+
+    private TranslationServiceInfo(@NonNull Context context, @NonNull ServiceInfo si) {
+        // Check for permission.
+        if (!Manifest.permission.BIND_TRANSLATION_SERVICE.equals(si.permission)) {
+            Slog.w(TAG, "TranslationServiceInfo from '" + si.packageName
+                    + "' does not require permission "
+                    + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+            throw new SecurityException("Service does not require permission "
+                    + Manifest.permission.BIND_CONTENT_CAPTURE_SERVICE);
+        }
+
+        mServiceInfo = si;
+
+        // Get the metadata, if declared.
+        // TODO: Try to find more easier way to do this.
+        final XmlResourceParser parser = si.loadXmlMetaData(context.getPackageManager(),
+                TranslationService.SERVICE_META_DATA);
+        if (parser == null) {
+            mSettingsActivity = null;
+            return;
+        }
+
+        String settingsActivity = null;
+
+        try {
+            final Resources resources = context.getPackageManager().getResourcesForApplication(
+                    si.applicationInfo);
+
+            int type = 0;
+            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+                type = parser.next();
+            }
+
+            if (XML_TAG_SERVICE.equals(parser.getName())) {
+                final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+                TypedArray afsAttributes = null;
+                try {
+                    afsAttributes = resources.obtainAttributes(allAttributes,
+                            com.android.internal.R.styleable.TranslationService);
+                    settingsActivity = afsAttributes.getString(
+                            R.styleable.ContentCaptureService_settingsActivity);
+                } finally {
+                    if (afsAttributes != null) {
+                        afsAttributes.recycle();
+                    }
+                }
+            } else {
+                Log.e(TAG, "Meta-data does not start with translation-service tag");
+            }
+        } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
+            Log.e(TAG, "Error parsing auto fill service meta-data", e);
+        }
+
+        mSettingsActivity = settingsActivity;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(getClass().getSimpleName());
+        builder.append("[").append(mServiceInfo);
+        builder.append(", settings:").append(mSettingsActivity);
+        return builder.toString();
+    }
+
+    /**
+     * Dumps the service information.
+     */
+    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        pw.print(prefix);
+        pw.print("Component: ");
+        pw.println(getServiceInfo().getComponentName());
+        pw.print(prefix);
+        pw.print("Settings: ");
+        pw.println(mSettingsActivity);
+    }
+}
diff --git a/core/java/android/view/translation/ITranslationDirectManager.aidl b/core/java/android/view/translation/ITranslationDirectManager.aidl
new file mode 100644
index 0000000..358f99a
--- /dev/null
+++ b/core/java/android/view/translation/ITranslationDirectManager.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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 android.service.translation.TranslationRequest;
+import android.service.translation.ITranslationCallback;
+import com.android.internal.os.IResultReceiver;
+
+/**
+  * Interface between an app (TranslationManager / Translator) and the remote TranslationService
+  * providing the TranslationService implementation.
+  *
+  * @hide
+  */
+oneway interface ITranslationDirectManager {
+    void onTranslationRequest(in TranslationRequest request, int sessionId,
+         in ITranslationCallback callback, in IResultReceiver receiver);
+    void onFinishTranslationSession(int sessionId);
+}
diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl
new file mode 100644
index 0000000..73addf4
--- /dev/null
+++ b/core/java/android/view/translation/ITranslationManager.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.service.translation.TranslationRequest;
+import android.view.translation.TranslationSpec;
+import com.android.internal.os.IResultReceiver;
+
+/**
+ * Mediator between apps being translated and translation service implementation.
+ *
+ * {@hide}
+ */
+oneway interface ITranslationManager {
+    void getSupportedLocales(in IResultReceiver receiver, int userId);
+    void onSessionCreated(in TranslationSpec sourceSpec, in TranslationSpec destSpec,
+         int sessionId, in IResultReceiver receiver, int userId);
+}
diff --git a/core/java/android/view/translation/OWNERS b/core/java/android/view/translation/OWNERS
new file mode 100644
index 0000000..a1e663a
--- /dev/null
+++ b/core/java/android/view/translation/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 994311
+
+adamhe@google.com
+augale@google.com
+joannechung@google.com
+lpeter@google.com
+svetoslavganov@google.com
+tymtsai@google.com
diff --git a/core/java/android/view/translation/TranslationData.aidl b/core/java/android/view/translation/TranslationData.aidl
new file mode 100644
index 0000000..40f21a6
--- /dev/null
+++ b/core/java/android/view/translation/TranslationData.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable TranslationData;
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
new file mode 100644
index 0000000..6554e1a
--- /dev/null
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -0,0 +1,201 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
+import android.annotation.SystemService;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.translation.TranslationService;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.SyncResultReceiver;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * The {@link TranslationManager} class provides ways for apps to integrate and use the
+ * translation framework.
+ *
+ * <p>The TranslationManager manages {@link Translator}s and help bridge client calls to
+ * the server {@link android.service.translation.TranslationService} </p>
+ */
+@SystemService(Context.TRANSLATION_MANAGER_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_TRANSLATION)
+public final class TranslationManager {
+
+    private static final String TAG = "TranslationManager";
+
+    /**
+     * Timeout for calls to system_server.
+     */
+    static final int SYNC_CALLS_TIMEOUT_MS = 5000;
+    /**
+     * The result code from result receiver success.
+     * @hide
+     */
+    public static final int STATUS_SYNC_CALL_SUCCESS = 1;
+    /**
+     * The result code from result receiver fail.
+     * @hide
+     */
+    public static final int STATUS_SYNC_CALL_FAIL = 2;
+
+    private static final Random ID_GENERATOR = new Random();
+    private final Object mLock = new Object();
+
+    @NonNull
+    private final Context mContext;
+
+    private final ITranslationManager mService;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private ITranslationDirectManager mDirectServiceBinder;
+
+    @NonNull
+    @GuardedBy("mLock")
+    private final SparseArray<Translator> mTranslators = new SparseArray<>();
+
+    @NonNull
+    @GuardedBy("mLock")
+    private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Integer> mTranslatorIds =
+            new ArrayMap<>();
+
+    @NonNull
+    private final Handler mHandler;
+
+    private static final AtomicInteger sAvailableRequestId = new AtomicInteger(1);
+
+    /**
+     * @hide
+     */
+    public TranslationManager(@NonNull Context context, ITranslationManager service) {
+        mContext = Objects.requireNonNull(context, "context cannot be null");
+        mService = service;
+
+        mHandler = Handler.createAsync(Looper.getMainLooper());
+    }
+
+    /**
+     * Create a Translator for translation.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * @param sourceSpec {@link TranslationSpec} for the data to be translated.
+     * @param destSpec {@link TranslationSpec} for the translated data.
+     * @return a {@link Translator} to be used for calling translation APIs.
+     */
+    @Nullable
+    @WorkerThread
+    public Translator createTranslator(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec) {
+        Objects.requireNonNull(sourceSpec, "sourceSpec cannot be null");
+        Objects.requireNonNull(sourceSpec, "destSpec cannot be null");
+
+        synchronized (mLock) {
+            // TODO(b/176464808): Disallow multiple Translator now, it will throw
+            //  IllegalStateException. Need to discuss if we can allow multiple Translators.
+            final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
+            if (mTranslatorIds.containsKey(specs)) {
+                return mTranslators.get(mTranslatorIds.get(specs));
+            }
+
+            int translatorId;
+            do {
+                translatorId = Math.abs(ID_GENERATOR.nextInt());
+            } while (translatorId == 0 || mTranslators.indexOfKey(translatorId) >= 0);
+
+            final Translator newTranslator = new Translator(mContext, sourceSpec, destSpec,
+                    translatorId, this, mHandler, mService);
+            // Start the Translator session and wait for the result
+            newTranslator.start();
+            try {
+                if (!newTranslator.isSessionCreated()) {
+                    return null;
+                }
+                mTranslators.put(translatorId, newTranslator);
+                mTranslatorIds.put(specs, translatorId);
+                return newTranslator;
+            } catch (Translator.ServiceBinderReceiver.TimeoutException e) {
+                // TODO(b/176464808): maybe make SyncResultReceiver.TimeoutException constructor
+                //  public and use it.
+                Log.e(TAG, "Timed out getting create session: " + e);
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Returns a list of locales supported by the {@link TranslationService}.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * TODO: Change to correct language/locale format
+     */
+    @NonNull
+    @WorkerThread
+    public List<String> getSupportedLocales() {
+        try {
+            // TODO: implement it
+            final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+            mService.getSupportedLocales(receiver, mContext.getUserId());
+            int resutCode = receiver.getIntResult();
+            if (resutCode != STATUS_SYNC_CALL_SUCCESS) {
+                return Collections.emptyList();
+            }
+            return receiver.getParcelableResult();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Timed out getting supported locales: " + e);
+            return Collections.emptyList();
+        }
+    }
+
+    void removeTranslator(int id) {
+        synchronized (mLock) {
+            mTranslators.remove(id);
+            for (int i = 0; i < mTranslatorIds.size(); i++) {
+                if (mTranslatorIds.valueAt(i) == id) {
+                    mTranslatorIds.removeAt(i);
+                    break;
+                }
+            }
+        }
+    }
+
+    AtomicInteger getAvailableRequestId() {
+        synchronized (mLock) {
+            return sAvailableRequestId;
+        }
+    }
+}
diff --git a/core/java/android/view/translation/TranslationRequest.aidl b/core/java/android/view/translation/TranslationRequest.aidl
new file mode 100644
index 0000000..c34bf30
--- /dev/null
+++ b/core/java/android/view/translation/TranslationRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable TranslationRequest;
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
new file mode 100644
index 0000000..a5e3f75
--- /dev/null
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -0,0 +1,215 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Wrapper class for data to be translated by {@link android.service.translation.TranslationService}
+ */
+@DataClass(genToString = true, genBuilder = true)
+public final class TranslationRequest implements Parcelable {
+
+    @Nullable
+    private final AutofillId mAutofillId;
+
+    @Nullable
+    private final CharSequence mTranslationText;
+
+    public TranslationRequest(@Nullable CharSequence text) {
+        mAutofillId = null;
+        mTranslationText = text;
+    }
+
+    private static CharSequence defaultTranslationText() {
+        return null;
+    }
+
+    private static AutofillId defaultAutofillId() {
+        return null;
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    /* package-private */ TranslationRequest(
+            @Nullable AutofillId autofillId,
+            @Nullable CharSequence translationText) {
+        this.mAutofillId = autofillId;
+        this.mTranslationText = translationText;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable AutofillId getAutofillId() {
+        return mAutofillId;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable CharSequence getTranslationText() {
+        return mTranslationText;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationRequest { " +
+                "autofillId = " + mAutofillId + ", " +
+                "translationText = " + mTranslationText +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mAutofillId != null) flg |= 0x1;
+        if (mTranslationText != null) flg |= 0x2;
+        dest.writeByte(flg);
+        if (mAutofillId != null) dest.writeTypedObject(mAutofillId, flags);
+        if (mTranslationText != null) dest.writeCharSequence(mTranslationText);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationRequest(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        AutofillId autofillId = (flg & 0x1) == 0 ? null : (AutofillId) in.readTypedObject(AutofillId.CREATOR);
+        CharSequence translationText = (flg & 0x2) == 0 ? null : (CharSequence) in.readCharSequence();
+
+        this.mAutofillId = autofillId;
+        this.mTranslationText = translationText;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationRequest> CREATOR
+            = new Parcelable.Creator<TranslationRequest>() {
+        @Override
+        public TranslationRequest[] newArray(int size) {
+            return new TranslationRequest[size];
+        }
+
+        @Override
+        public TranslationRequest createFromParcel(@NonNull android.os.Parcel in) {
+            return new TranslationRequest(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationRequest}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private @Nullable AutofillId mAutofillId;
+        private @Nullable CharSequence mTranslationText;
+
+        private long mBuilderFieldsSet = 0L;
+
+        public Builder() {
+        }
+
+        @DataClass.Generated.Member
+        public @NonNull Builder setAutofillId(@NonNull AutofillId value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mAutofillId = value;
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslationText(@NonNull CharSequence value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mTranslationText = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationRequest build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x1) == 0) {
+                mAutofillId = defaultAutofillId();
+            }
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mTranslationText = defaultTranslationText();
+            }
+            TranslationRequest o = new TranslationRequest(
+                    mAutofillId,
+                    mTranslationText);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1610060189421L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequest.java",
+            inputSignatures = "private final @android.annotation.Nullable android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.Nullable java.lang.CharSequence mTranslationText\nprivate static  java.lang.CharSequence defaultTranslationText()\nprivate static  android.view.autofill.AutofillId defaultAutofillId()\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genBuilder=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationResponse.aidl b/core/java/android/view/translation/TranslationResponse.aidl
new file mode 100644
index 0000000..e5350bb
--- /dev/null
+++ b/core/java/android/view/translation/TranslationResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable TranslationResponse;
diff --git a/core/java/android/view/translation/TranslationResponse.java b/core/java/android/view/translation/TranslationResponse.java
new file mode 100644
index 0000000..d29063f
--- /dev/null
+++ b/core/java/android/view/translation/TranslationResponse.java
@@ -0,0 +1,311 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.translation.TranslationService;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Response from the {@link TranslationService}, which contains the translated result.
+ */
+@DataClass(genBuilder = true, genToString = true, genHiddenConstDefs = true)
+public final class TranslationResponse implements Parcelable {
+
+    /**
+     * The {@link TranslationService} was successful in translating.
+     */
+    public static final int TRANSLATION_STATUS_SUCCESS = 0;
+    /**
+     * The {@link TranslationService} returned unknown translation result.
+     */
+    public static final int TRANSLATION_STATUS_UNKNOWN_ERROR = 1;
+    /**
+     * The language of the request is not available to be translated.
+     */
+    public static final int TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE = 2;
+
+    /**
+     * The translation result status code.
+     */
+    private final @TranslationStatus int mTranslationStatus;
+    /**
+     * The translation results. If there is no translation result, set it with an empty list.
+     */
+    @NonNull
+    private List<TranslationRequest> mTranslations = new ArrayList();
+
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationResponse.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "TRANSLATION_STATUS_", value = {
+        TRANSLATION_STATUS_SUCCESS,
+        TRANSLATION_STATUS_UNKNOWN_ERROR,
+        TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface TranslationStatus {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String translationStatusToString(@TranslationStatus int value) {
+        switch (value) {
+            case TRANSLATION_STATUS_SUCCESS:
+                    return "TRANSLATION_STATUS_SUCCESS";
+            case TRANSLATION_STATUS_UNKNOWN_ERROR:
+                    return "TRANSLATION_STATUS_UNKNOWN_ERROR";
+            case TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE:
+                    return "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    /* package-private */ TranslationResponse(
+            @TranslationStatus int translationStatus,
+            @NonNull List<TranslationRequest> translations) {
+        this.mTranslationStatus = translationStatus;
+
+        if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+                && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+                && !(mTranslationStatus == TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE)) {
+            throw new java.lang.IllegalArgumentException(
+                    "translationStatus was " + mTranslationStatus + " but must be one of: "
+                            + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+                            + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+                            + "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE(" + TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE + ")");
+        }
+
+        this.mTranslations = translations;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslations);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The translation result status code.
+     */
+    @DataClass.Generated.Member
+    public @TranslationStatus int getTranslationStatus() {
+        return mTranslationStatus;
+    }
+
+    /**
+     * The translation results. If there is no translation result, set it with an empty list.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<TranslationRequest> getTranslations() {
+        return mTranslations;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "TranslationResponse { " +
+                "translationStatus = " + translationStatusToString(mTranslationStatus) + ", " +
+                "translations = " + mTranslations +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mTranslationStatus);
+        dest.writeParcelableList(mTranslations, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationResponse(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int translationStatus = in.readInt();
+        List<TranslationRequest> translations = new ArrayList<>();
+        in.readParcelableList(translations, TranslationRequest.class.getClassLoader());
+
+        this.mTranslationStatus = translationStatus;
+
+        if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+                && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+                && !(mTranslationStatus == TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE)) {
+            throw new java.lang.IllegalArgumentException(
+                    "translationStatus was " + mTranslationStatus + " but must be one of: "
+                            + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+                            + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+                            + "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE(" + TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE + ")");
+        }
+
+        this.mTranslations = translations;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mTranslations);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationResponse> CREATOR
+            = new Parcelable.Creator<TranslationResponse>() {
+        @Override
+        public TranslationResponse[] newArray(int size) {
+            return new TranslationResponse[size];
+        }
+
+        @Override
+        public TranslationResponse createFromParcel(@NonNull Parcel in) {
+            return new TranslationResponse(in);
+        }
+    };
+
+    /**
+     * A builder for {@link TranslationResponse}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private @TranslationStatus int mTranslationStatus;
+        private @NonNull List<TranslationRequest> mTranslations;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param translationStatus
+         *   The translation result status code.
+         */
+        public Builder(
+                @TranslationStatus int translationStatus) {
+            mTranslationStatus = translationStatus;
+
+            if (!(mTranslationStatus == TRANSLATION_STATUS_SUCCESS)
+                    && !(mTranslationStatus == TRANSLATION_STATUS_UNKNOWN_ERROR)
+                    && !(mTranslationStatus == TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE)) {
+                throw new java.lang.IllegalArgumentException(
+                        "translationStatus was " + mTranslationStatus + " but must be one of: "
+                                + "TRANSLATION_STATUS_SUCCESS(" + TRANSLATION_STATUS_SUCCESS + "), "
+                                + "TRANSLATION_STATUS_UNKNOWN_ERROR(" + TRANSLATION_STATUS_UNKNOWN_ERROR + "), "
+                                + "TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE(" + TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE + ")");
+            }
+
+        }
+
+        /**
+         * The translation result status code.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslationStatus(@TranslationStatus int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mTranslationStatus = value;
+            return this;
+        }
+
+        /**
+         * The translation results. If there is no translation result, set it with an empty list.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTranslations(@NonNull List<TranslationRequest> value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mTranslations = value;
+            return this;
+        }
+
+        /** @see #setTranslations */
+        @DataClass.Generated.Member
+        public @NonNull Builder addTranslations(@NonNull TranslationRequest value) {
+            // You can refine this method's name by providing item's singular name, e.g.:
+            // @DataClass.PluralOf("item")) mItems = ...
+
+            if (mTranslations == null) setTranslations(new ArrayList<>());
+            mTranslations.add(value);
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull TranslationResponse build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            if ((mBuilderFieldsSet & 0x2) == 0) {
+                mTranslations = new ArrayList();
+            }
+            TranslationResponse o = new TranslationResponse(
+                    mTranslationStatus,
+                    mTranslations);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x4) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1609973911361L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponse.java",
+            inputSignatures = "public static final  int TRANSLATION_STATUS_SUCCESS\npublic static final  int TRANSLATION_STATUS_UNKNOWN_ERROR\npublic static final  int TRANSLATION_STATUS_LANGUAGE_UNAVAILABLE\nprivate final @android.view.translation.TranslationResponse.TranslationStatus int mTranslationStatus\nprivate @android.annotation.NonNull java.util.List<android.view.translation.TranslationRequest> mTranslations\nclass TranslationResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/view/translation/TranslationSpec.aidl b/core/java/android/view/translation/TranslationSpec.aidl
new file mode 100644
index 0000000..875d798
--- /dev/null
+++ b/core/java/android/view/translation/TranslationSpec.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable TranslationSpec;
diff --git a/core/java/android/view/translation/TranslationSpec.java b/core/java/android/view/translation/TranslationSpec.java
new file mode 100644
index 0000000..ab1bc47
--- /dev/null
+++ b/core/java/android/view/translation/TranslationSpec.java
@@ -0,0 +1,189 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Specs and additional info for the translation data.
+ *
+ * <p>This spec help specify information such as the language/locale for the translation, as well
+ * as the data format for the translation (text, audio, etc.)</p>
+ */
+@DataClass(genEqualsHashCode = true, genHiddenConstDefs = true)
+public final class TranslationSpec implements Parcelable {
+
+    /** Data format for translation is text. */
+    public static final int DATA_FORMAT_TEXT = 1;
+
+    /** @hide */
+    @android.annotation.IntDef(prefix = "DATA_FORMAT_", value = {
+            DATA_FORMAT_TEXT
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface DataFormat {}
+
+    /**
+     * String representation of language codes e.g. "en", "es", etc.
+     */
+    private final @NonNull String mLanguage;
+
+    private final @DataFormat int mDataFormat;
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/translation/TranslationSpec.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new TranslationSpec.
+     *
+     * @param language
+     *   String representation of language codes e.g. "en", "es", etc.
+     */
+    @DataClass.Generated.Member
+    public TranslationSpec(
+            @NonNull String language,
+            @DataFormat int dataFormat) {
+        this.mLanguage = language;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLanguage);
+        this.mDataFormat = dataFormat;
+        com.android.internal.util.AnnotationValidations.validate(
+                DataFormat.class, null, mDataFormat);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * String representation of language codes e.g. "en", "es", etc.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getLanguage() {
+        return mLanguage;
+    }
+
+    @DataClass.Generated.Member
+    public @DataFormat int getDataFormat() {
+        return mDataFormat;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(TranslationSpec other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        TranslationSpec that = (TranslationSpec) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mLanguage, that.mLanguage)
+                && mDataFormat == that.mDataFormat;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mLanguage);
+        _hash = 31 * _hash + mDataFormat;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeString(mLanguage);
+        dest.writeInt(mDataFormat);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ TranslationSpec(@NonNull Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        String language = in.readString();
+        int dataFormat = in.readInt();
+
+        this.mLanguage = language;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLanguage);
+        this.mDataFormat = dataFormat;
+        com.android.internal.util.AnnotationValidations.validate(
+                DataFormat.class, null, mDataFormat);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<TranslationSpec> CREATOR
+            = new Parcelable.Creator<TranslationSpec>() {
+        @Override
+        public TranslationSpec[] newArray(int size) {
+            return new TranslationSpec[size];
+        }
+
+        @Override
+        public TranslationSpec createFromParcel(@NonNull Parcel in) {
+            return new TranslationSpec(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1609964630624L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/view/translation/TranslationSpec.java",
+            inputSignatures = "public static final  int DATA_FORMAT_TEXT\nprivate final @android.annotation.NonNull java.lang.String mLanguage\nprivate final @android.view.translation.TranslationSpec.DataFormat int mDataFormat\nclass TranslationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
new file mode 100644
index 0000000..675f32b
--- /dev/null
+++ b/core/java/android/view/translation/Translator.java
@@ -0,0 +1,298 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.SyncResultReceiver;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
+ */
+@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 TranslationSpec mSourceSpec;
+
+    @NonNull
+    private final TranslationSpec mDestSpec;
+
+    @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 {
+        private final WeakReference<Translator> mTranslator;
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private int mSessionId;
+
+        ServiceBinderReceiver(Translator translator) {
+            mTranslator = new WeakReference<>(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();
+                return;
+            }
+            mSessionId = resultData.getInt(EXTRA_SESSION_ID);
+            final Translator translator = mTranslator.get();
+            if (translator == null) {
+                Log.w(TAG, "received result after session is finished");
+                return;
+            }
+            final IBinder binder;
+            if (resultData != null) {
+                binder = resultData.getBinder(EXTRA_SERVICE_BINDER);
+                if (binder == null) {
+                    Log.wtf(TAG, "No " + EXTRA_SERVICE_BINDER + " extra result");
+                    return;
+                }
+            } else {
+                binder = null;
+            }
+            translator.setServiceBinder(binder);
+            mLatch.countDown();
+        }
+
+        // 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 TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec, int sessionId,
+            @NonNull TranslationManager translationManager, @NonNull Handler handler,
+            @Nullable ITranslationManager systemServerBinder) {
+        mContext = context;
+        mSourceSpec = sourceSpec;
+        mDestSpec = destSpec;
+        mId = sessionId;
+        mManager = translationManager;
+        mHandler = handler;
+        mSystemServerBinder = systemServerBinder;
+        mServiceBinderReceiver = new ServiceBinderReceiver(this);
+    }
+
+    /**
+     * Starts this Translator session.
+     */
+    void start() {
+        try {
+            mSystemServerBinder.onSessionCreated(mSourceSpec, mDestSpec, 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 int getTranslatorId() {
+        return mId;
+    }
+
+    /**
+     * Requests a translation for the provided {@link TranslationRequest} using the Translator's
+     * source spec and destination spec.
+     *
+     * <p><strong>NOTE: </strong>Call on a worker thread.
+     *
+     * @param request {@link TranslationRequest} request to be translated.
+     *
+     * @return {@link TranslationRequest} containing translated request,
+     *         or null if translation could not be done.
+     * @throws IllegalStateException if this TextClassification session was destroyed when calls
+     */
+    @Nullable
+    @WorkerThread
+    public TranslationResponse translate(@NonNull TranslationRequest request) {
+        Objects.requireNonNull(request, "Translation request 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 ArrayList<TranslationRequest> requests = new ArrayList<>();
+        requests.add(request);
+        final android.service.translation.TranslationRequest internalRequest =
+                new android.service.translation.TranslationRequest
+                        .Builder(getNextRequestId(), mSourceSpec, mDestSpec, requests)
+                        .build();
+
+        TranslationResponse response = null;
+        try {
+            final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS);
+            mDirectServiceBinder.onTranslationRequest(internalRequest, mId, null, receiver);
+
+            response = receiver.getParcelableResult();
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling requestTranslate(): " + e);
+        }  catch (SyncResultReceiver.TimeoutException e) {
+            Log.e(TAG, "Timed out calling requestTranslate: " + e);
+        }
+        if (sDEBUG) {
+            Log.v(TAG, "Receive translation response: " + response);
+        }
+        return response;
+    }
+
+    /**
+     * 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.
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8682fea..b5c4586 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3591,6 +3591,14 @@
     <permission android:name="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.translation.TranslationService,
+         to ensure that only the system can bind to it.
+         @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+     -->
+    <permission android:name="android.permission.BIND_TRANSLATION_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.contentsuggestions.ContentSuggestionsService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7ca3faf..ef54db1a 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8292,6 +8292,23 @@
     </declare-styleable>
 
     <!-- =============================== -->
+    <!-- Translation attributes -->
+    <!-- =============================== -->
+    <eat-comment />
+
+    <!-- Use <code>translation-service</code> as the root tag of the XML resource that describes
+         a {@link android.service.translation.TranslationService}, which is referenced from
+         its {@link android.service.translation.TranslationService#SERVICE_META_DATA} meta-data
+         entry.
+         @hide @SystemApi
+    -->
+    <declare-styleable name="TranslationService">
+        <!-- Fully qualified class name of an activity that allows the user to modify
+             the settings for this service. -->
+        <attr name="settingsActivity" />
+    </declare-styleable>
+
+    <!-- =============================== -->
     <!-- Contacts meta-data attributes -->
     <!-- =============================== -->
     <eat-comment />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index da658cc..cff1bda 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3724,6 +3724,14 @@
     -->
     <string name="config_defaultAugmentedAutofillService" translatable="false"></string>
 
+    <!-- The package name for the system's translation service.
+     This service must be trusted, as it can be activated without explicit consent of the user.
+     If no service with the specified name exists on the device, translation wil be
+     disabled.
+     Example: "com.android.translation/.TranslationService"
+-->
+    <string name="config_defaultTranslationService" translatable="false"></string>
+
     <!-- The package name for the system's app prediction service.
          This service must be trusted, as it can be activated without explicit consent of the user.
          Example: "com.android.intelligence/.AppPredictionService"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fa66451..9dadd5f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3479,6 +3479,7 @@
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
   <java-symbol type="string" name="config_defaultAugmentedAutofillService" />
+  <java-symbol type="string" name="config_defaultTranslationService" />
   <java-symbol type="string" name="config_defaultAppPredictionService" />
   <java-symbol type="string" name="config_defaultContentSuggestionsService" />
   <java-symbol type="string" name="config_defaultSearchUiService" />
diff --git a/services/Android.bp b/services/Android.bp
index 3447b5c..947bd00 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -30,6 +30,7 @@
         ":services.searchui-sources",
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
+        ":services.translation-sources",
         ":services.usage-sources",
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
@@ -76,6 +77,7 @@
         "services.searchui",
         "services.startop",
         "services.systemcaptions",
+        "services.translation",
         "services.usage",
         "services.usb",
         "services.voiceinteraction",
diff --git a/services/translation/Android.bp b/services/translation/Android.bp
new file mode 100644
index 0000000..804a617
--- /dev/null
+++ b/services/translation/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+    name: "services.translation-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.translation",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.translation-sources"],
+    libs: ["services.core"],
+}
\ No newline at end of file
diff --git a/services/translation/OWNERS b/services/translation/OWNERS
new file mode 100644
index 0000000..a1e663a
--- /dev/null
+++ b/services/translation/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 994311
+
+adamhe@google.com
+augale@google.com
+joannechung@google.com
+lpeter@google.com
+svetoslavganov@google.com
+tymtsai@google.com
diff --git a/services/translation/java/com/android/server/translation/RemoteTranslationService.java b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
new file mode 100644
index 0000000..0c7e617
--- /dev/null
+++ b/services/translation/java/com/android/server/translation/RemoteTranslationService.java
@@ -0,0 +1,87 @@
+/*
+ * 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 com.android.server.translation;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.translation.ITranslationService;
+import android.service.translation.TranslationService;
+import android.util.Slog;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.os.IResultReceiver;
+
+final class RemoteTranslationService extends ServiceConnector.Impl<ITranslationService> {
+
+    private static final String TAG = RemoteTranslationService.class.getSimpleName();
+
+    // TODO(b/176590870): Make PERMANENT now.
+    private static final long TIMEOUT_IDLE_UNBIND_MS =
+            AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+    private static final int TIMEOUT_REQUEST_MS = 5_000;
+
+    private final long mIdleUnbindTimeoutMs;
+    private final int mRequestTimeoutMs;
+    private final ComponentName mComponentName;
+
+    RemoteTranslationService(Context context, ComponentName serviceName,
+            int userId, boolean bindInstantServiceAllowed) {
+        super(context,
+                new Intent(TranslationService.SERVICE_INTERFACE).setComponent(serviceName),
+                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+                userId, ITranslationService.Stub::asInterface);
+        mIdleUnbindTimeoutMs = TIMEOUT_IDLE_UNBIND_MS;
+        mRequestTimeoutMs = TIMEOUT_REQUEST_MS;
+        mComponentName = serviceName;
+
+        // Bind right away.
+        connect();
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected void onServiceConnectionStatusChanged(ITranslationService service,
+            boolean connected) {
+        try {
+            if (connected) {
+                service.onConnected();
+            } else {
+                service.onDisconnected();
+            }
+        } catch (Exception e) {
+            Slog.w(TAG,
+                    "Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e);
+        }
+    }
+
+    @Override // from AbstractRemoteService
+    protected long getAutoDisconnectTimeoutMs() {
+        return mIdleUnbindTimeoutMs;
+    }
+
+    public void onSessionCreated(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+        run((s) -> s.onCreateTranslationSession(sourceSpec, destSpec, sessionId, resultReceiver));
+    }
+}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java
new file mode 100644
index 0000000..e2aabe6
--- /dev/null
+++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java
@@ -0,0 +1,92 @@
+/*
+ * 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 com.android.server.translation;
+
+import static android.content.Context.TRANSLATION_MANAGER_SERVICE;
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.translation.ITranslationManager;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.os.IResultReceiver;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+/**
+ * Entry point service for translation management.
+ *
+ * <p>This service provides the {@link ITranslationManager} implementation and keeps a list of
+ * {@link TranslationManagerServiceImpl} per user; the real work is done by
+ * {@link TranslationManagerServiceImpl} itself.
+ */
+public final class TranslationManagerService
+        extends AbstractMasterSystemService<TranslationManagerService,
+        TranslationManagerServiceImpl> {
+
+    private static final String TAG = "TranslationManagerService";
+
+    public TranslationManagerService(Context context) {
+        // TODO: Discuss the disallow policy
+        super(context, new FrameworkResourcesServiceNameResolver(context,
+                        com.android.internal.R.string.config_defaultTranslationService),
+                /* disallowProperty */ null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+    }
+
+    @Override
+    protected TranslationManagerServiceImpl newServiceLocked(int resolvedUserId, boolean disabled) {
+        return new TranslationManagerServiceImpl(this, mLock, resolvedUserId, disabled);
+    }
+
+    final class TranslationManagerServiceStub extends ITranslationManager.Stub {
+        @Override
+        public void getSupportedLocales(IResultReceiver receiver, int userId)
+                throws RemoteException {
+            synchronized (mLock) {
+                final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.getSupportedLocalesLocked(receiver);
+                } else {
+                    Slog.v(TAG, "getSupportedLocales(): no service for " + userId);
+                    receiver.send(STATUS_SYNC_CALL_FAIL, null);
+                }
+            }
+        }
+
+        @Override
+        public void onSessionCreated(TranslationSpec sourceSpec, TranslationSpec destSpec,
+                int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
+            synchronized (mLock) {
+                final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver);
+                } else {
+                    Slog.v(TAG, "onSessionCreated(): no service for " + userId);
+                    receiver.send(STATUS_SYNC_CALL_FAIL, null);
+                }
+            }
+        }
+    }
+
+    @Override // from SystemService
+    public void onStart() {
+        publishBinderService(TRANSLATION_MANAGER_SERVICE,
+                new TranslationManagerService.TranslationManagerServiceStub());
+    }
+}
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
new file mode 100644
index 0000000..b1f6f80
--- /dev/null
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -0,0 +1,125 @@
+/*
+ * 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 com.android.server.translation;
+
+import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_SUCCESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.service.translation.TranslationServiceInfo;
+import android.util.Slog;
+import android.view.translation.TranslationSpec;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.SyncResultReceiver;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.util.ArrayList;
+
+final class TranslationManagerServiceImpl extends
+        AbstractPerUserSystemService<TranslationManagerServiceImpl, TranslationManagerService> {
+
+    private static final String TAG = "TranslationManagerServiceImpl";
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteTranslationService mRemoteTranslationService;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private ServiceInfo mRemoteTranslationServiceInfo;
+
+    protected TranslationManagerServiceImpl(
+            @NonNull TranslationManagerService master,
+            @NonNull Object lock, int userId, boolean disabled) {
+        super(master, lock, userId);
+        updateRemoteServiceLocked();
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        final TranslationServiceInfo info = new TranslationServiceInfo(getContext(),
+                serviceComponent, isTemporaryServiceSetLocked(), mUserId);
+        mRemoteTranslationServiceInfo = info.getServiceInfo();
+        return info.getServiceInfo();
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        updateRemoteServiceLocked();
+        return enabledChanged;
+    }
+
+    /**
+     * Updates the reference to the remote service.
+     */
+    @GuardedBy("mLock")
+    private void updateRemoteServiceLocked() {
+        if (mRemoteTranslationService != null) {
+            if (mMaster.debug) Slog.d(TAG, "updateRemoteService(): destroying old remote service");
+            mRemoteTranslationService.unbind();
+            mRemoteTranslationService = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteTranslationService ensureRemoteServiceLocked() {
+        if (mRemoteTranslationService == null) {
+            final String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
+                }
+                return null;
+            }
+            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+            mRemoteTranslationService = new RemoteTranslationService(getContext(),
+                    serviceComponent, mUserId, /* isInstantAllowed= */ false);
+        }
+        return mRemoteTranslationService;
+    }
+
+    @GuardedBy("mLock")
+    void getSupportedLocalesLocked(@NonNull IResultReceiver resultReceiver) {
+        // TODO: implement this
+        try {
+            resultReceiver.send(STATUS_SYNC_CALL_SUCCESS,
+                    SyncResultReceiver.bundleFor(new ArrayList<>()));
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException returning supported locales: " + e);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void onSessionCreatedLocked(@NonNull TranslationSpec sourceSpec,
+            @NonNull TranslationSpec destSpec, int sessionId, IResultReceiver resultReceiver) {
+        final RemoteTranslationService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onSessionCreated(sourceSpec, destSpec, sessionId, resultReceiver);
+        }
+    }
+}