Merge "Add shell commands and dump information for Translation" into sc-dev
This commit is contained in:
@@ -7119,6 +7119,9 @@ public class Activity extends ContextThemeWrapper
|
||||
case "--contentcapture":
|
||||
dumpContentCaptureManager(prefix, writer);
|
||||
return;
|
||||
case "--translation":
|
||||
dumpUiTranslation(prefix, writer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
writer.print(prefix); writer.print("Local Activity ");
|
||||
@@ -7159,6 +7162,7 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
dumpAutofillManager(prefix, writer);
|
||||
dumpContentCaptureManager(prefix, writer);
|
||||
dumpUiTranslation(prefix, writer);
|
||||
|
||||
ResourcesManager.getInstance().dump(prefix, writer);
|
||||
}
|
||||
@@ -7183,6 +7187,14 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
}
|
||||
|
||||
void dumpUiTranslation(String prefix, PrintWriter writer) {
|
||||
if (mUiTranslationController != null) {
|
||||
mUiTranslationController.dump(prefix, writer);
|
||||
} else {
|
||||
writer.print(prefix); writer.println("No UiTranslationController");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit indicating that this activity is "immersive" and should not be
|
||||
* interrupted by notifications if possible.
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.android.internal.util.DataClass;
|
||||
* <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)
|
||||
@DataClass(genEqualsHashCode = true, genHiddenConstDefs = true, genToString = true)
|
||||
public final class TranslationSpec implements Parcelable {
|
||||
|
||||
/** Data format for translation is text. */
|
||||
@@ -97,6 +97,18 @@ public final class TranslationSpec implements Parcelable {
|
||||
return mDataFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public String toString() {
|
||||
// You can override field toString logic by defining methods like:
|
||||
// String fieldNameToString() { ... }
|
||||
|
||||
return "TranslationSpec { " +
|
||||
"language = " + mLanguage + ", " +
|
||||
"dataFormat = " + mDataFormat +
|
||||
" }";
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public boolean equals(@android.annotation.Nullable Object o) {
|
||||
@@ -175,10 +187,10 @@ public final class TranslationSpec implements Parcelable {
|
||||
};
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1609964630624L,
|
||||
time = 1614326090637L,
|
||||
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)")
|
||||
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, genToString=true)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.util.SyncResultReceiver;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -221,6 +222,12 @@ public class Translator {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
|
||||
pw.print(prefix); pw.print("sourceSpec: "); pw.println(mSourceSpec);
|
||||
pw.print(prefix); pw.print("destSpec: "); pw.println(mDestSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a translation for the provided {@link TranslationRequest} using the Translator's
|
||||
* source spec and destination spec.
|
||||
|
||||
@@ -40,6 +40,7 @@ import android.view.translation.UiTranslationManager.UiTranslationState;
|
||||
|
||||
import com.android.internal.util.function.pooled.PooledLambda;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -132,6 +133,23 @@ public class UiTranslationController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to dump the translation information for Activity.
|
||||
*/
|
||||
public void dump(String outerPrefix, PrintWriter pw) {
|
||||
pw.print(outerPrefix); pw.println("UiTranslationController:");
|
||||
final String pfx = outerPrefix + " ";
|
||||
pw.print(pfx); pw.print("activity: "); pw.println(mActivity);
|
||||
final int translatorSize = mTranslators.size();
|
||||
pw.print(outerPrefix); pw.print("number translator: "); pw.println(translatorSize);
|
||||
for (int i = 0; i < translatorSize; i++) {
|
||||
pw.print(outerPrefix); pw.print("#"); pw.println(i);
|
||||
final Translator translator = mTranslators.valueAt(i);
|
||||
translator.dump(outerPrefix, pw);
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method is used by {@link Translator}, it will be called when the translation is done. The
|
||||
* translation result can be get from here.
|
||||
|
||||
@@ -483,6 +483,8 @@ applications that come with the platform
|
||||
<permission name="android.permission.SIGNAL_REBOOT_READINESS" />
|
||||
<!-- Permission required for CTS test - PeopleManagerTest -->
|
||||
<permission name="android.permission.READ_PEOPLE_DATA" />
|
||||
<!-- Permission required for CTS test - UiTranslationManagerTest -->
|
||||
<permission name="android.permission.MANAGE_UI_TRANSLATION" />
|
||||
</privapp-permissions>
|
||||
|
||||
<privapp-permissions package="com.android.statementservice">
|
||||
|
||||
@@ -164,6 +164,7 @@
|
||||
<uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_SEARCH_UI" />
|
||||
<uses-permission android:name="android.permission.MANAGE_SMARTSPACE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_UI_TRANSLATION" />
|
||||
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.SET_TIME" />
|
||||
|
||||
@@ -20,19 +20,28 @@ import static android.Manifest.permission.MANAGE_UI_TRANSLATION;
|
||||
import static android.content.Context.TRANSLATION_MANAGER_SERVICE;
|
||||
import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ShellCallback;
|
||||
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.annotations.GuardedBy;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.server.infra.AbstractMasterSystemService;
|
||||
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -48,6 +57,8 @@ public final class TranslationManagerService
|
||||
|
||||
private static final String TAG = "TranslationManagerService";
|
||||
|
||||
private static final int MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS = 2 * 60_000; // 2 minutes
|
||||
|
||||
public TranslationManagerService(Context context) {
|
||||
// TODO: Discuss the disallow policy
|
||||
super(context, new FrameworkResourcesServiceNameResolver(context,
|
||||
@@ -60,19 +71,82 @@ public final class TranslationManagerService
|
||||
return new TranslationManagerServiceImpl(this, mLock, resolvedUserId, disabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enforceCallingPermissionForManagement() {
|
||||
getContext().enforceCallingPermission(MANAGE_UI_TRANSLATION, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMaximumTemporaryServiceDurationMs() {
|
||||
return MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dumpLocked(String prefix, PrintWriter pw) {
|
||||
super.dumpLocked(prefix, pw);
|
||||
}
|
||||
|
||||
private void enforceCallerHasPermission(String permission) {
|
||||
final String msg = "Permission Denial from pid =" + Binder.getCallingPid() + ", uid="
|
||||
+ Binder.getCallingUid() + " doesn't hold " + permission;
|
||||
getContext().enforceCallingPermission(permission, msg);
|
||||
}
|
||||
|
||||
/** True if the currently set handler service is not overridden by the shell. */
|
||||
@GuardedBy("mLock")
|
||||
private boolean isDefaultServiceLocked(int userId) {
|
||||
final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId);
|
||||
if (defaultServiceName == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String currentServiceName = mServiceNameResolver.getServiceName(userId);
|
||||
return defaultServiceName.equals(currentServiceName);
|
||||
}
|
||||
|
||||
/** True if the caller of the api is the same app which hosts the TranslationService. */
|
||||
@GuardedBy("mLock")
|
||||
private boolean isCalledByServiceAppLocked(int userId, @NonNull String methodName) {
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
|
||||
final String serviceName = mServiceNameResolver.getServiceName(userId);
|
||||
if (serviceName == null) {
|
||||
Slog.e(TAG, methodName + ": called by UID " + callingUid
|
||||
+ ", but there's no service set for user " + userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
|
||||
if (serviceComponent == null) {
|
||||
Slog.w(TAG, methodName + ": invalid service name: " + serviceName);
|
||||
return false;
|
||||
}
|
||||
|
||||
final String servicePackageName = serviceComponent.getPackageName();
|
||||
final PackageManager pm = getContext().getPackageManager();
|
||||
final int serviceUid;
|
||||
try {
|
||||
serviceUid = pm.getPackageUidAsUser(servicePackageName, userId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Slog.w(TAG, methodName + ": could not verify UID for " + serviceName);
|
||||
return false;
|
||||
}
|
||||
if (callingUid != serviceUid) {
|
||||
Slog.e(TAG, methodName + ": called by UID " + callingUid + ", but service UID is "
|
||||
+ serviceUid);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (service != null && (isDefaultServiceLocked(userId)
|
||||
|| isCalledByServiceAppLocked(userId, "getSupportedLocales"))) {
|
||||
service.getSupportedLocalesLocked(receiver);
|
||||
} else {
|
||||
Slog.v(TAG, "getSupportedLocales(): no service for " + userId);
|
||||
@@ -86,7 +160,8 @@ public final class TranslationManagerService
|
||||
int sessionId, IResultReceiver receiver, int userId) throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
if (service != null && (isDefaultServiceLocked(userId)
|
||||
|| isCalledByServiceAppLocked(userId, "onSessionCreated"))) {
|
||||
service.onSessionCreatedLocked(sourceSpec, destSpec, sessionId, receiver);
|
||||
} else {
|
||||
Slog.v(TAG, "onSessionCreated(): no service for " + userId);
|
||||
@@ -102,12 +177,35 @@ public final class TranslationManagerService
|
||||
enforceCallerHasPermission(MANAGE_UI_TRANSLATION);
|
||||
synchronized (mLock) {
|
||||
final TranslationManagerServiceImpl service = getServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
if (service != null && (isDefaultServiceLocked(userId)
|
||||
|| isCalledByServiceAppLocked(userId, "updateUiTranslationState"))) {
|
||||
service.updateUiTranslationState(state, sourceSpec, destSpec, viewIds,
|
||||
taskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the service state into the given stream. You run "adb shell dumpsys translation".
|
||||
*/
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
synchronized (mLock) {
|
||||
dumpLocked("", pw);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShellCommand(@Nullable FileDescriptor in,
|
||||
@Nullable FileDescriptor out,
|
||||
@Nullable FileDescriptor err,
|
||||
@NonNull String[] args,
|
||||
@Nullable ShellCallback callback,
|
||||
@NonNull ResultReceiver resultReceiver) throws RemoteException {
|
||||
new TranslationManagerServiceShellCommand(
|
||||
TranslationManagerService.this).exec(this, in, out, err, args, callback,
|
||||
resultReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from SystemService
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.os.ShellCommand;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/** Handles adb shell commands send to TranslationManagerService. */
|
||||
public class TranslationManagerServiceShellCommand extends ShellCommand {
|
||||
private final TranslationManagerService mService;
|
||||
|
||||
TranslationManagerServiceShellCommand(TranslationManagerService service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(String cmd) {
|
||||
if (cmd == null) {
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
if ("set".equals(cmd)) {
|
||||
return requestSet(pw);
|
||||
}
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
|
||||
private int requestSet(PrintWriter pw) {
|
||||
final String what = getNextArgRequired();
|
||||
if ("temporary-service".equals(what)) {
|
||||
return setTemporaryService(pw);
|
||||
}
|
||||
pw.println("Invalid set: " + what);
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int setTemporaryService(PrintWriter pw) {
|
||||
final int userId = Integer.parseInt(getNextArgRequired());
|
||||
final String serviceName = getNextArg();
|
||||
if (serviceName == null) {
|
||||
mService.resetTemporaryService(userId);
|
||||
return 0;
|
||||
}
|
||||
final int duration = Integer.parseInt(getNextArgRequired());
|
||||
mService.setTemporaryService(userId, serviceName, duration);
|
||||
pw.println("TranslationService temporarily set to " + serviceName + " for "
|
||||
+ duration + "ms");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHelp() {
|
||||
try (PrintWriter pw = getOutPrintWriter();) {
|
||||
pw.println("Translation Service (translation) commands:");
|
||||
pw.println(" help");
|
||||
pw.println(" Prints this help text.");
|
||||
pw.println("");
|
||||
pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
|
||||
pw.println(" Temporarily (for DURATION ms) changes the service implementation.");
|
||||
pw.println(" To reset, call with just the USER_ID argument.");
|
||||
pw.println("");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user