Refactored AdServicesShellCommandHandler to remove unused stuff.
It's logic is still based on BasicShellCommandHandler though.
Test: atest AdServicesApkUnitTests:AdServicesShellCommandHandlerTest
Bug: 283481948
Fixes: 311134328
Change-Id: If30ff5fd3fa0dd60deba349f45c5cdc78a2f41f5
diff --git a/adservices/apk/java/com/android/adservices/common/AdServicesCommonService.java b/adservices/apk/java/com/android/adservices/common/AdServicesCommonService.java
index 628acc7..f0905ce 100644
--- a/adservices/apk/java/com/android/adservices/common/AdServicesCommonService.java
+++ b/adservices/apk/java/com/android/adservices/common/AdServicesCommonService.java
@@ -104,7 +104,7 @@
LogUtil.w(
"Using dump to call AdServicesShellCommandHandler - should NOT happen on"
+ " production");
- new AdServicesShellCommandHandler(/* context= */ this, fd).run(realArgs);
+ new AdServicesShellCommandHandler(/* context= */ this, pw).run(realArgs);
return;
}
super.dump(fd, pw, args);
diff --git a/adservices/apk/java/com/android/adservices/common/AdServicesShellCommandHandler.java b/adservices/apk/java/com/android/adservices/common/AdServicesShellCommandHandler.java
index 297c2a5..6be5048 100644
--- a/adservices/apk/java/com/android/adservices/common/AdServicesShellCommandHandler.java
+++ b/adservices/apk/java/com/android/adservices/common/AdServicesShellCommandHandler.java
@@ -17,31 +17,34 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.os.Binder;
import android.text.TextUtils;
import android.util.Log;
import com.android.adservices.service.common.AppManifestConfigHelper;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.utils.BasicShellCommandHandler;
-import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Objects;
-import java.util.function.Supplier;
// TODO(b/308009734): STOPSHIP - document that it's up to each command implementation to check about
// caller's permission, whether device is userdebug, etc...
+
+// TODO(b/308009734): arg parsing logic mostly copied from on BasicShellCommandHandler, it might be
+// worth to refactor it once the handler is called by both system_server and service.
/**
* Service-side version of {@code cmd adservices_manager}.
*
* <p>By convention, methods implementing commands should be prefixed with {@code run}.
*/
-public final class AdServicesShellCommandHandler extends BasicShellCommandHandler {
+public final class AdServicesShellCommandHandler {
- private static final String TAG = AdServicesShellCommandHandler.class.getSimpleName();
+ // TODO(b/280460130): use adservice helpers for tag name / logging methods
+ private static final String TAG = "AdServicesShellCmd";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ @VisibleForTesting static final String CMD_SHORT_HELP = "-h";
+ @VisibleForTesting static final String CMD_HELP = "help";
@VisibleForTesting static final String CMD_ECHO = "echo";
@VisibleForTesting
@@ -77,8 +80,7 @@
+ " enrollment id is allowed to use the Topics APIs in the given app, when"
+ " using SDK sandbox or not.";
- @VisibleForTesting
- static final String ERROR_EMPTY_COMMAND = "Must provide a non-empty command\n";
+ @VisibleForTesting static final String ERROR_EMPTY_COMMAND = "Must provide a non-empty command";
@VisibleForTesting
static final String ERROR_TEMPLATE_INVALID_ARGS = "Invalid cmd (%s). Syntax: %s";
@@ -87,38 +89,15 @@
private static final int RESULT_GENERIC_ERROR = -1;
private final Context mContext;
- private final FileDescriptor mFd;
+ private final PrintWriter mOut;
- // Used only on tests (otherwise it would be hard to get content of FileDescriptor)
- @Nullable private final Supplier<PrintWriter> mSupplier;
+ private String[] mArgs;
+ private int mArgPos;
+ private String mCurArgData;
- public AdServicesShellCommandHandler(Context context, FileDescriptor fd) {
- this(context, Objects.requireNonNull(fd, "fd cannot be null"), /* supplier= */ null);
- }
-
- @VisibleForTesting
- AdServicesShellCommandHandler(Context context, Supplier<PrintWriter> supplier) {
- this(
- context,
- FileDescriptor.out,
- Objects.requireNonNull(supplier, "supplier cannot be null"));
- }
-
- private AdServicesShellCommandHandler(
- Context context, FileDescriptor fd, Supplier<PrintWriter> supplier) {
+ public AdServicesShellCommandHandler(Context context, PrintWriter out) {
mContext = Objects.requireNonNull(context, "context cannot be null");
- mFd = fd;
- mSupplier = supplier;
- }
-
- @Override
- public PrintWriter getOutPrintWriter() {
- if (mSupplier != null) {
- PrintWriter supplied = mSupplier.get();
- Log.i(TAG, "getOutPrintWriter(): returning supplied object: " + supplied);
- return supplied;
- }
- return super.getOutPrintWriter();
+ mOut = Objects.requireNonNull(out, "out cannot be null");
}
/** Runs the given command ({@code args[0]}) and optional arguments */
@@ -129,45 +108,109 @@
throw new IllegalArgumentException(
"must have at least one argument (the command itself)");
}
+ if (DEBUG) {
+ Log.d(TAG, "run(): " + Arrays.toString(args));
+ }
+ mArgs = args;
+ String cmd = getNextArg();
- return super.exec(/* target= */ null, /* in= */ null, /* out= */ mFd, /* err= */ mFd, args);
+ int res = RESULT_GENERIC_ERROR;
+ try {
+ res = onCommand(cmd);
+ if (DEBUG) {
+ Log.d(TAG, "Executed command " + cmd);
+ }
+ } catch (Throwable e) {
+ // TODO(b/308009734): need to test this
+ mOut.printf("Exception occurred while executing %s\n", Arrays.toString(mArgs));
+ e.printStackTrace(mOut);
+ } finally {
+ if (DEBUG) {
+ Log.d(TAG, "Flushing output");
+ }
+ mOut.flush();
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Sending command result: " + res);
+ }
+ return res;
}
- @Override
- public int exec(
- Binder target,
- FileDescriptor in,
- FileDescriptor out,
- FileDescriptor err,
- String[] args) {
- throw new UnsupportedOperationException("not exposed");
+ /******************
+ * Helper methods *
+ ******************/
+
+ @Nullable
+ private String getNextArg() {
+ if (mArgs == null) {
+ throw new IllegalStateException("INTERNAL ERROR: getNextArg() called before run()");
+ }
+ if (mCurArgData != null) {
+ String arg = mCurArgData;
+ mCurArgData = null;
+ return arg;
+ } else if (mArgPos < mArgs.length) {
+ return mArgs[mArgPos++];
+ } else {
+ return null;
+ }
}
- @Override
- public void onHelp() {
- PrintWriter pw = getOutPrintWriter();
-
- pw.println(HELP_ECHO);
- pw.println(HELP_IS_ALLOWED_ATTRIBUTION_ACCESS);
- pw.println(HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS);
- pw.println(HELP_IS_ALLOWED_TOPICS_ACCESS);
+ private boolean hasExactNumberOfArgs(int expected) {
+ return mArgs.length == expected + 1; // adds +1 for the cmd itself
}
- @Override
- public int onCommand(String cmd) {
- Log.d(TAG, "onCommand: " + cmd);
+ @Nullable
+ private Boolean getNextBooleanArg() {
+ String arg = getNextArg();
+ if (TextUtils.isEmpty(arg)) {
+ return null;
+ }
+ // Boolean.parse returns false when it's invalid
+ switch (arg.trim().toLowerCase()) {
+ case "true":
+ return Boolean.TRUE;
+ case "false":
+ return Boolean.FALSE;
+ default:
+ return null;
+ }
+ }
+
+ private int invalidArgsError(String syntax) {
+ mOut.println(String.format(ERROR_TEMPLATE_INVALID_ARGS, Arrays.toString(mArgs), syntax));
+ return RESULT_GENERIC_ERROR;
+ }
+
+ /****************************************************************************
+ * Commands - for each new one, add onHelp(), onCommand(), and runCommand() *
+ ****************************************************************************/
+
+ private void onHelp() {
+ mOut.println(HELP_ECHO);
+ mOut.println(HELP_IS_ALLOWED_ATTRIBUTION_ACCESS);
+ mOut.println(HELP_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS);
+ mOut.println(HELP_IS_ALLOWED_TOPICS_ACCESS);
+ }
+
+ private int onCommand(String cmd) {
switch (cmd) {
+ case CMD_SHORT_HELP:
+ case CMD_HELP:
+ onHelp();
+ return RESULT_GENERIC_ERROR;
+ case "":
+ mOut.println(ERROR_EMPTY_COMMAND);
+ return RESULT_GENERIC_ERROR;
case CMD_ECHO:
return runEcho();
case CMD_IS_ALLOWED_ATTRIBUTION_ACCESS:
case CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS:
case CMD_IS_ALLOWED_TOPICS_ACCESS:
return runIsAllowedApiAccess(cmd);
- case "":
- getOutPrintWriter().print(ERROR_EMPTY_COMMAND);
- return RESULT_GENERIC_ERROR;
default:
- return handleDefaultCommands(cmd);
+ mOut.printf("Unknown command: %s\n", cmd);
+ return RESULT_GENERIC_ERROR;
}
}
@@ -181,7 +224,7 @@
}
Log.i(TAG, "runEcho: message='" + message + "'");
- getOutPrintWriter().println(message);
+ mOut.println(message);
return RESULT_OK;
}
@@ -260,38 +303,7 @@
+ isValid);
break;
}
- getOutPrintWriter().println(isValid);
+ mOut.println(isValid);
return RESULT_OK;
}
-
- private boolean hasExactNumberOfArgs(int expected) {
- return getAllArgs().length == expected + 1; // adds +1 for the cmd itself
- }
-
- @Nullable
- private Boolean getNextBooleanArg() {
- String arg = getNextArg();
- if (TextUtils.isEmpty(arg)) {
- return null;
- }
- // Boolean.parse returns false when it's invalid
- switch (arg.trim().toLowerCase()) {
- case "true":
- return Boolean.TRUE;
- case "false":
- return Boolean.FALSE;
- default:
- return null;
- }
- }
-
- private int invalidArgsError(String syntax) {
- getOutPrintWriter()
- .println(
- String.format(
- ERROR_TEMPLATE_INVALID_ARGS,
- Arrays.toString(getAllArgs()),
- syntax));
- return RESULT_GENERIC_ERROR;
- }
}
diff --git a/adservices/apk/unittest/src/com/android/adservices/common/AdServicesShellCommandHandlerTest.java b/adservices/apk/unittest/src/com/android/adservices/common/AdServicesShellCommandHandlerTest.java
index 0f3d4f1..2d328be 100644
--- a/adservices/apk/unittest/src/com/android/adservices/common/AdServicesShellCommandHandlerTest.java
+++ b/adservices/apk/unittest/src/com/android/adservices/common/AdServicesShellCommandHandlerTest.java
@@ -17,9 +17,11 @@
package com.android.adservices.common;
import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_ECHO;
+import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_HELP;
import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_IS_ALLOWED_ATTRIBUTION_ACCESS;
import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_IS_ALLOWED_CUSTOM_AUDIENCES_ACCESS;
import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_IS_ALLOWED_TOPICS_ACCESS;
+import static com.android.adservices.common.AdServicesShellCommandHandler.CMD_SHORT_HELP;
import static com.android.adservices.common.AdServicesShellCommandHandler.ERROR_EMPTY_COMMAND;
import static com.android.adservices.common.AdServicesShellCommandHandler.ERROR_TEMPLATE_INVALID_ARGS;
import static com.android.adservices.common.AdServicesShellCommandHandler.HELP_ECHO;
@@ -28,13 +30,10 @@
import static com.android.adservices.common.AdServicesShellCommandHandler.HELP_IS_ALLOWED_TOPICS_ACCESS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import android.content.Context;
-import android.os.Binder;
import com.android.adservices.mockito.AdServicesExtendedMockitoRule;
import com.android.adservices.service.common.AppManifestConfigHelper;
@@ -44,7 +43,6 @@
import org.junit.Rule;
import org.junit.Test;
-import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -71,10 +69,12 @@
public void testInvalidConstructor() throws Exception {
assertThrows(
NullPointerException.class,
- () -> new AdServicesShellCommandHandler(mCmd.context, (FileDescriptor) null));
+ () -> new AdServicesShellCommandHandler(mCmd.context, (PrintWriter) null));
assertThrows(
NullPointerException.class,
- () -> new AdServicesShellCommandHandler(/* context= */ null, FileDescriptor.out));
+ () ->
+ new AdServicesShellCommandHandler(
+ /* context= */ null, mock(PrintWriter.class)));
}
@Test
@@ -84,42 +84,15 @@
}
@Test
- public void testExec() throws Exception {
- assertThrows(
- UnsupportedOperationException.class,
- () ->
- mCmd.cmd.exec(
- new Binder(),
- FileDescriptor.in,
- FileDescriptor.out,
- FileDescriptor.err,
- /* args= */ (String[]) null));
- }
-
- @Test
- public void testErrWriterIsSameAsOutWriter() throws Exception {
- PrintWriter outWriter = mCmd.cmd.getOutPrintWriter();
-
- assertWithMessage("err").that(mCmd.cmd.getErrPrintWriter()).isSameInstanceAs(outWriter);
- }
-
- @Test
- public void testOnHelp() throws Exception {
- mCmd.cmd.onHelp();
-
- assertHelpContents(mCmd.getOut());
- }
-
- @Test
public void testRunHelp() throws Exception {
- String result = mCmd.runInvalid("help");
+ String result = mCmd.runInvalid(CMD_HELP);
assertHelpContents(result);
}
@Test
public void testRunHelpShort() throws Exception {
- String result = mCmd.runInvalid("-h");
+ String result = mCmd.runInvalid(CMD_SHORT_HELP);
assertHelpContents(result);
}
@@ -128,7 +101,7 @@
public void testRun_noCommand() throws Exception {
String result = mCmd.runInvalid("");
- expect.withMessage("result of '%s'").that(result).isEqualTo(ERROR_EMPTY_COMMAND);
+ expect.withMessage("result of ''").that(result).isEqualTo(ERROR_EMPTY_COMMAND + "\n");
}
@Test
@@ -328,7 +301,7 @@
public final Context context = mock(Context.class);
public final AdServicesShellCommandHandler cmd =
- new AdServicesShellCommandHandler(context, () -> mOut);
+ new AdServicesShellCommandHandler(context, mOut);
private boolean mOutCalled;
diff --git a/adservices/service-core/Android.bp b/adservices/service-core/Android.bp
index df5278d..49161d2 100644
--- a/adservices/service-core/Android.bp
+++ b/adservices/service-core/Android.bp
@@ -172,7 +172,6 @@
"adservices-shared-error-logging",
"adservices-shared-storage",
"modules-utils-build",
- "modules-utils-shell-command-handler",
],
jarjar_rules: "jarjar-rules.txt",
apex_available: ["com.android.adservices", "com.android.extservices"],