diff options
8 files changed, 260 insertions, 0 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 730fce7c049a..141a78f4d47f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -137,6 +137,7 @@ package android { field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS"; field public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission.MANAGE_TIME_AND_ZONE_DETECTION"; + field public static final String MANAGE_UI_TRANSLATION = "android.permission.MANAGE_UI_TRANSLATION"; field public static final String MANAGE_USB = "android.permission.MANAGE_USB"; field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS"; field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE"; @@ -1943,6 +1944,7 @@ package android.content { 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 UI_TRANSLATION_SERVICE = "ui_translation"; 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"; @@ -13405,6 +13407,17 @@ package android.view.contentcapture { } +package android.view.translation { + + public final class UiTranslationManager { + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int); + } + +} + package android.webkit { public abstract class CookieManager { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index dd016a2cf78e..e62cd4f60186 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -211,6 +211,7 @@ import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; import android.view.translation.ITranslationManager; import android.view.translation.TranslationManager; +import android.view.translation.UiTranslationManager; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; @@ -1188,6 +1189,19 @@ public final class SystemServiceRegistry { return null; }}); + registerService(Context.UI_TRANSLATION_SERVICE, UiTranslationManager.class, + new CachedServiceFetcher<UiTranslationManager>() { + @Override + public UiTranslationManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getService(Context.TRANSLATION_MANAGER_SERVICE); + ITranslationManager service = ITranslationManager.Stub.asInterface(b); + if (service != null) { + return new UiTranslationManager(ctx.getOuterContext(), service); + } + return null; + }}); + registerService(Context.SEARCH_UI_SERVICE, SearchUiManager.class, new CachedServiceFetcher<SearchUiManager>() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5ccceca9a69d..5736985b9220 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4520,6 +4520,15 @@ public abstract class Context { public static final String TRANSLATION_MANAGER_SERVICE = "transformer"; /** + * Official published name of the translation service which supports ui translation function. + * + * @hide + * @see #getSystemService(String) + */ + @SystemApi + public static final String UI_TRANSLATION_SERVICE = "ui_translation"; + + /** * Used for getting content selections and classifications for task snapshots. * * @hide diff --git a/core/java/android/view/translation/ITranslationManager.aidl b/core/java/android/view/translation/ITranslationManager.aidl index 73addf4d1894..e1754531d761 100644 --- a/core/java/android/view/translation/ITranslationManager.aidl +++ b/core/java/android/view/translation/ITranslationManager.aidl @@ -16,10 +16,15 @@ package android.view.translation; +import android.content.ComponentName; +import android.os.IBinder; import android.service.translation.TranslationRequest; +import android.view.autofill.AutofillId; import android.view.translation.TranslationSpec; import com.android.internal.os.IResultReceiver; +import java.util.List; + /** * Mediator between apps being translated and translation service implementation. * @@ -29,4 +34,8 @@ 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); + + void updateUiTranslationState(int state, in TranslationSpec sourceSpec, + in TranslationSpec destSpec, in List<AutofillId> viewIds, in int taskId, + int userId); } diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java new file mode 100644 index 000000000000..eeb463ae0ed3 --- /dev/null +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -0,0 +1,179 @@ +/* + * 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.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.RemoteException; +import android.view.View; +import android.view.autofill.AutofillId; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Objects; + +/** + * The {@link UiTranslationManager} class provides ways for apps to use the ui translation + * function in framework. + * + * @hide + */ +@SystemApi +public final class UiTranslationManager { + + private static final String TAG = "UiTranslationManager"; + + /** + * The state caller request to disable utranslation,, it is no longer need to ui translation. + * + * @hide + */ + public static final int STATE_UI_TRANSLATION_STARTED = 0; + /** + * The state caller request to pause ui translation, it will switch back to the original text. + * + * @hide + */ + public static final int STATE_UI_TRANSLATION_PAUSED = 1; + /** + * The state caller request to resume the paused ui translation, it will show the translated + * text again if the text had been translated. + * + * @hide + */ + public static final int STATE_UI_TRANSLATION_RESUMED = 2; + /** + * The state the caller request to enable ui translation. + * + * @hide + */ + public static final int STATE_UI_TRANSLATION_FINISHED = 3; + /** + * @hide + */ + @IntDef(prefix = {"STATE__TRANSLATION"}, value = { + STATE_UI_TRANSLATION_STARTED, + STATE_UI_TRANSLATION_PAUSED, + STATE_UI_TRANSLATION_RESUMED, + STATE_UI_TRANSLATION_FINISHED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UiTranslationState { + } + + @NonNull + private final Context mContext; + + private final ITranslationManager mService; + + /** + * @hide + */ + public UiTranslationManager(@NonNull Context context, ITranslationManager service) { + mContext = Objects.requireNonNull(context); + mService = service; + } + + /** + * Request ui translation for a given Views. + * + * @param sourceSpec {@link TranslationSpec} for the data to be translated. + * @param destSpec {@link TranslationSpec} for the translated data. + * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated + * @param taskId the Activity Task id which needs ui translation + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void startTranslation(@NonNull TranslationSpec sourceSpec, + @NonNull TranslationSpec destSpec, @NonNull List<AutofillId> viewIds, + int taskId) { + // TODO(b/177789967): Return result code or find a way to notify the status. + // TODO(b/177394471): The is a temparary API, the expected is requestUiTranslation( + // TranslationSpec, TranslationSpec,List<AutofillId>, Binder). We may need more time to + // implement it, use task id as initial version for demo. + Objects.requireNonNull(sourceSpec); + Objects.requireNonNull(destSpec); + Objects.requireNonNull(viewIds); + + try { + mService.updateUiTranslationState(STATE_UI_TRANSLATION_STARTED, sourceSpec, + destSpec, viewIds, taskId, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to disable the ui translation. It will destroy all the {@link Translator}s and no + * longer to show to show the translated text. + * + * @param taskId the Activity Task id which needs ui translation + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void finishTranslation(int taskId) { + try { + // TODO(b/177394471): The is a temparary API, the expected is finishUiTranslation( + // Binder). We may need more time to implement it, use task id as initial version. + mService.updateUiTranslationState(STATE_UI_TRANSLATION_FINISHED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, + mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to pause the current ui translation's {@link Translator} which will switch back to + * the original language. + * + * @param taskId the Activity Task id which needs ui translation + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void pauseTranslation(int taskId) { + try { + // TODO(b/177394471): The is a temparary API, the expected is pauseUiTranslation(Binder) + // We may need more time to implement it, use task id as initial version for demo + mService.updateUiTranslationState(STATE_UI_TRANSLATION_PAUSED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, taskId, + mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to resume the paused ui translation's {@link Translator} which will switch to the + * translated language if the text had been translated. + * + * @param taskId the Activity Task id which needs ui translation + */ + @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) + public void resumeTranslation(int taskId) { + try { + // TODO(b/177394471): The is a temparary API, the expected is resumeUiTranslation( + // Binder). We may need more time to implement it, use task id as initial version. + mService.updateUiTranslationState(STATE_UI_TRANSLATION_RESUMED, + null /* sourceSpec */, null /* destSpec*/, null /* viewIds */, + taskId, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c18e15ef9f5c..05fbe4baba1a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3599,6 +3599,15 @@ <permission android:name="android.permission.BIND_TRANSLATION_SERVICE" android:protectionLevel="signature" /> + <!-- @SystemApi Allows apps to use ui translation functions. + <p>Protection level: signature|privileged + <p> TODO(b/177703453): Restrict to a specific Role instead of allowing access to any + privileged app. + @hide Not for use by third-party applications. + --> + <permission android:name="android.permission.MANAGE_UI_TRANSLATION" + android:protectionLevel="signature|privileged" /> + <!-- 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/services/translation/java/com/android/server/translation/TranslationManagerService.java b/services/translation/java/com/android/server/translation/TranslationManagerService.java index e2aabe6a89ea..84c6e7be16be 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerService.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerService.java @@ -22,13 +22,17 @@ 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.autofill.AutofillId; import android.view.translation.ITranslationManager; import android.view.translation.TranslationSpec; +import android.view.translation.UiTranslationManager.UiTranslationState; import com.android.internal.os.IResultReceiver; import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.FrameworkResourcesServiceNameResolver; +import java.util.List; + /** * Entry point service for translation management. * @@ -82,6 +86,19 @@ public final class TranslationManagerService } } } + + @Override + public void updateUiTranslationState(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, + int taskId, int userId) { + synchronized (mLock) { + final TranslationManagerServiceImpl service = getServiceForUserLocked(userId); + if (service != null) { + service.updateUiTranslationState(state, sourceSpec, destSpec, viewIds, + taskId); + } + } + } } @Override // from SystemService diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java index b1f6f80d4158..5b1074fc4c0a 100644 --- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java +++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java @@ -26,7 +26,9 @@ import android.content.pm.ServiceInfo; import android.os.RemoteException; import android.service.translation.TranslationServiceInfo; import android.util.Slog; +import android.view.autofill.AutofillId; import android.view.translation.TranslationSpec; +import android.view.translation.UiTranslationManager.UiTranslationState; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.IResultReceiver; @@ -34,6 +36,7 @@ import com.android.internal.util.SyncResultReceiver; import com.android.server.infra.AbstractPerUserSystemService; import java.util.ArrayList; +import java.util.List; final class TranslationManagerServiceImpl extends AbstractPerUserSystemService<TranslationManagerServiceImpl, TranslationManagerService> { @@ -122,4 +125,11 @@ final class TranslationManagerServiceImpl extends remoteService.onSessionCreated(sourceSpec, destSpec, sessionId, resultReceiver); } } + + @GuardedBy("mLock") + public void updateUiTranslationState(@UiTranslationState int state, + TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> viewIds, + int taskId) { + // TODO: implement this in next change + } } |