Merge "Add shell commands and dump information for Translation" into sc-dev

This commit is contained in:
Joanne Chung
2021-03-02 11:39:07 +00:00
committed by Android (Google) Code Review
8 changed files with 235 additions and 6 deletions

View File

@@ -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.

View File

@@ -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() {}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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">

View File

@@ -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" />

View File

@@ -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

View File

@@ -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("");
}
}
}