OMS: limit shell command output via optional args

Update the list and dump commands to optionally limit their output to make it
easier to use the commands in automated tests.

Examples of the new arguments in use:

  $ adb shell cmd overlay list android
  android
  [ ] com.android.internal.display.cutout.emulation.corner
  [ ] com.android.internal.display.cutout.emulation.double
  [ ] com.android.theme.icon.square
  ...

  $ adb shell cmd overlay dump com.android.theme.icon.square
  com.android.theme.icon.square:0 {
    mPackageName...........: com.android.theme.icon.square
    mUserId................: 0
    mTargetPackageName.....: android
    ...
  }

  $ adb shell cmd overlay dump state com.android.theme.icon.square
  STATE_DISABLED

Bug: 130364252
Test: atest 'com.android.server.om.hosttest.InstallOverlayTests#testAdbShellOMSInterface'
Change-Id: Iee7d654e2600bb13b7755ce1623925df68f43463
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
index 267cb36..eec7be2 100644
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -177,6 +177,23 @@
                 .contains(APP_OVERLAY_PACKAGE_NAME));
     }
 
+    @Test
+    public void testAdbShellOMSInterface() throws Exception {
+        installPackage("OverlayHostTests_AppOverlayV1.apk");
+        assertTrue(shell("cmd overlay list " + DEVICE_TEST_PKG).contains(DEVICE_TEST_PKG));
+        assertTrue(shell("cmd overlay list " + DEVICE_TEST_PKG).contains(APP_OVERLAY_PACKAGE_NAME));
+        assertEquals("[ ] " + APP_OVERLAY_PACKAGE_NAME,
+                shell("cmd overlay list " + APP_OVERLAY_PACKAGE_NAME).trim());
+        assertEquals("STATE_DISABLED",
+                shell("cmd overlay dump state " + APP_OVERLAY_PACKAGE_NAME).trim());
+
+        setOverlayEnabled(APP_OVERLAY_PACKAGE_NAME, true);
+        assertEquals("[x] " + APP_OVERLAY_PACKAGE_NAME,
+                shell("cmd overlay list " + APP_OVERLAY_PACKAGE_NAME).trim());
+        assertEquals("STATE_ENABLED",
+                shell("cmd overlay dump state " + APP_OVERLAY_PACKAGE_NAME).trim());
+    }
+
     private void delay() {
         try {
             Thread.sleep(1000);
@@ -195,20 +212,24 @@
     }
 
     private void installConvertExistingInstantPackageToFull(String pkg) throws Exception {
-        getDevice().executeShellCommand("cmd package install-existing --wait --full " + pkg);
+        shell("cmd package install-existing --wait --full " + pkg);
     }
 
     private void setPackageEnabled(String pkg, boolean enabled) throws Exception {
-        getDevice().executeShellCommand("cmd package " + (enabled ? "enable " : "disable ") + pkg);
+        shell("cmd package " + (enabled ? "enable " : "disable ") + pkg);
         delay();
     }
 
     private void setOverlayEnabled(String pkg, boolean enabled) throws Exception {
-        getDevice().executeShellCommand("cmd overlay " + (enabled ? "enable " : "disable ") + pkg);
+        shell("cmd overlay " + (enabled ? "enable " : "disable ") + pkg);
         delay();
     }
 
     private boolean overlayManagerContainsPackage(String pkg) throws Exception {
-        return getDevice().executeShellCommand("cmd overlay list").contains(pkg);
+        return shell("cmd overlay list").contains(pkg);
+    }
+
+    private String shell(final String cmd) throws Exception {
+        return getDevice().executeShellCommand(cmd);
     }
 }
diff --git a/services/core/java/com/android/server/om/DumpState.java b/services/core/java/com/android/server/om/DumpState.java
new file mode 100644
index 0000000..1e2e054
--- /dev/null
+++ b/services/core/java/com/android/server/om/DumpState.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 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.om;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+
+/**
+ * State for dumps performed by the OverlayManagerService.
+ */
+public final class DumpState {
+    @UserIdInt private int mUserId = UserHandle.USER_ALL;
+    @Nullable private String mPackageName;
+    @Nullable private String mField;
+    private boolean mVerbose;
+
+    /** Sets the user to dump the state for */
+    public void setUserId(@UserIdInt int userId) {
+        mUserId = userId;
+    }
+    @UserIdInt public int getUserId() {
+        return mUserId;
+    }
+
+    /** Sets the name of the package to dump the state for */
+    public void setPackageName(String packageName) {
+        mPackageName = packageName;
+    }
+    @Nullable public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Sets the name of the field to dump the state of */
+    public void setField(String field) {
+        mField = field;
+    }
+    @Nullable public String getField() {
+        return mField;
+    }
+
+    /** Enables verbose dump state */
+    public void setVerbose(boolean verbose) {
+        mVerbose = verbose;
+    }
+    public boolean isVerbose() {
+        return mVerbose;
+    }
+}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 209ccda..da69986 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -749,15 +749,77 @@
         }
 
         @Override
-        protected void dump(@NonNull final FileDescriptor fd, @NonNull final PrintWriter pw,
-                @NonNull final String[] argv) {
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            final DumpState dumpState = new DumpState();
+            dumpState.setUserId(UserHandle.getUserId(Binder.getCallingUid()));
+
+            int opti = 0;
+            while (opti < args.length) {
+                final String opt = args[opti];
+                if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                    break;
+                }
+                opti++;
+
+                if ("-h".equals(opt)) {
+                    pw.println("dump [-h] [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
+                    pw.println("  Print debugging information about the overlay manager.");
+                    pw.println("  With optional parameter PACKAGE, limit output to the specified");
+                    pw.println("  package. With optional parameter FIELD, limit output to");
+                    pw.println("  the value of that SettingsItem field. Field names are");
+                    pw.println("  case insensitive and out.println the m prefix can be omitted,");
+                    pw.println("  so the following are equivalent: mState, mstate, State, state.");
+                    return;
+                } else if ("--user".equals(opt)) {
+                    opti++;
+                    if (opti >= args.length) {
+                        pw.println("Error: user missing argument");
+                        return;
+                    }
+                    try {
+                        dumpState.setUserId(Integer.parseInt(args[opti]));
+                    } catch (NumberFormatException e) {
+                        pw.println("Error: user argument is not a number: " + args[opti]);
+                        return;
+                    }
+                } else if ("--verbose".equals(opt)) {
+                    dumpState.setVerbose(true);
+                } else {
+                    pw.println("Unknown argument: " + opt + "; use -h for help");
+                }
+            }
+            if (opti < args.length) {
+                final String arg = args[opti];
+                opti++;
+                switch (arg) {
+                    case "packagename":
+                    case "userid":
+                    case "targetpackagename":
+                    case "targetoverlayablename":
+                    case "basecodepath":
+                    case "state":
+                    case "isenabled":
+                    case "isstatic":
+                    case "priority":
+                    case "category":
+                        dumpState.setField(arg);
+                        break;
+                    default:
+                        dumpState.setPackageName(arg);
+                        break;
+                }
+            }
+            if (dumpState.getPackageName() == null && opti < args.length) {
+                dumpState.setPackageName(args[opti]);
+                opti++;
+            }
+
             enforceDumpPermission("dump");
-
-            final boolean verbose = argv.length > 0 && "--verbose".equals(argv[0]);
-
             synchronized (mLock) {
-                mImpl.onDump(pw);
-                mPackageManager.dump(pw, verbose);
+                mImpl.dump(pw, dumpState);
+                if (dumpState.getPackageName() == null) {
+                    mPackageManager.dump(pw, dumpState);
+                }
             }
         }
 
@@ -1046,10 +1108,10 @@
         private static final String TAB1 = "    ";
         private static final String TAB2 = TAB1 + TAB1;
 
-        public void dump(@NonNull final PrintWriter pw, final boolean verbose) {
+        public void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
             pw.println("PackageInfo cache");
 
-            if (!verbose) {
+            if (!dumpState.isVerbose()) {
                 int count = 0;
                 final int n = mCache.size();
                 for (int i = 0; i < n; i++) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 092dbc8..6f28675 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -636,9 +636,11 @@
         return true;
     }
 
-    void onDump(@NonNull final PrintWriter pw) {
-        mSettings.dump(pw);
-        pw.println("Default overlays: " + TextUtils.join(";", mDefaultOverlays));
+    void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
+        mSettings.dump(pw, dumpState);
+        if (dumpState.getPackageName() == null) {
+            pw.println("Default overlays: " + TextUtils.join(";", mDefaultOverlays));
+        }
     }
 
     @NonNull String[] getDefaultOverlayPackages() {
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index f35c707..b7346d4 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.om.OverlayInfo;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.Xml;
@@ -288,35 +289,78 @@
         return true;
     }
 
-    void dump(@NonNull final PrintWriter p) {
-        final IndentingPrintWriter pw = new IndentingPrintWriter(p, "  ");
-        pw.println("Settings");
-        pw.increaseIndent();
-
-        if (mItems.isEmpty()) {
-            pw.println("<none>");
-            return;
+    void dump(@NonNull final PrintWriter p, @NonNull DumpState dumpState) {
+        // select items to display
+        Stream<SettingsItem> items = mItems.stream();
+        if (dumpState.getUserId() != UserHandle.USER_ALL) {
+            items = items.filter(item -> item.mUserId == dumpState.getUserId());
+        }
+        if (dumpState.getPackageName() != null) {
+            items = items.filter(item -> item.mPackageName.equals(dumpState.getPackageName()));
         }
 
-        final int n = mItems.size();
-        for (int i = 0; i < n; i++) {
-            final SettingsItem item = mItems.get(i);
-            pw.println(item.mPackageName + ":" + item.getUserId() + " {");
-            pw.increaseIndent();
+        // display items
+        final IndentingPrintWriter pw = new IndentingPrintWriter(p, "  ");
+        if (dumpState.getField() != null) {
+            items.forEach(item -> dumpSettingsItemField(pw, item, dumpState.getField()));
+        } else {
+            items.forEach(item -> dumpSettingsItem(pw, item));
+        }
+    }
 
-            pw.println("mPackageName...........: " + item.mPackageName);
-            pw.println("mUserId................: " + item.getUserId());
-            pw.println("mTargetPackageName.....: " + item.getTargetPackageName());
-            pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName());
-            pw.println("mBaseCodePath..........: " + item.getBaseCodePath());
-            pw.println("mState.................: " + OverlayInfo.stateToString(item.getState()));
-            pw.println("mIsEnabled.............: " + item.isEnabled());
-            pw.println("mIsStatic..............: " + item.isStatic());
-            pw.println("mPriority..............: " + item.mPriority);
-            pw.println("mCategory..............: " + item.mCategory);
+    private void dumpSettingsItem(@NonNull final IndentingPrintWriter pw,
+            @NonNull final SettingsItem item) {
+        pw.println(item.mPackageName + ":" + item.getUserId() + " {");
+        pw.increaseIndent();
 
-            pw.decreaseIndent();
-            pw.println("}");
+        pw.println("mPackageName...........: " + item.mPackageName);
+        pw.println("mUserId................: " + item.getUserId());
+        pw.println("mTargetPackageName.....: " + item.getTargetPackageName());
+        pw.println("mTargetOverlayableName.: " + item.getTargetOverlayableName());
+        pw.println("mBaseCodePath..........: " + item.getBaseCodePath());
+        pw.println("mState.................: " + OverlayInfo.stateToString(item.getState()));
+        pw.println("mIsEnabled.............: " + item.isEnabled());
+        pw.println("mIsStatic..............: " + item.isStatic());
+        pw.println("mPriority..............: " + item.mPriority);
+        pw.println("mCategory..............: " + item.mCategory);
+
+        pw.decreaseIndent();
+        pw.println("}");
+    }
+
+    private void dumpSettingsItemField(@NonNull final IndentingPrintWriter pw,
+            @NonNull final SettingsItem item, @NonNull final String field) {
+        switch (field) {
+            case "packagename":
+                pw.println(item.mPackageName);
+                break;
+            case "userid":
+                pw.println(item.mUserId);
+                break;
+            case "targetpackagename":
+                pw.println(item.mTargetPackageName);
+                break;
+            case "targetoverlayablename":
+                pw.println(item.mTargetOverlayableName);
+                break;
+            case "basecodepath":
+                pw.println(item.mBaseCodePath);
+                break;
+            case "state":
+                pw.println(OverlayInfo.stateToString(item.mState));
+                break;
+            case "isenabled":
+                pw.println(item.mIsEnabled);
+                break;
+            case "isstatic":
+                pw.println(item.mIsStatic);
+                break;
+            case "priority":
+                pw.println(item.mPriority);
+                break;
+            case "category":
+                pw.println(item.mCategory);
+                break;
         }
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index e2b1cba..7ee167a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -77,13 +77,17 @@
         out.println("Overlay manager (overlay) commands:");
         out.println("  help");
         out.println("    Print this help text.");
-        out.println("  dump [--verbose] [--user USER_ID] [PACKAGE [PACKAGE [...]]]");
+        out.println("  dump [--verbose] [--user USER_ID] [[FIELD] PACKAGE]");
         out.println("    Print debugging information about the overlay manager.");
-        out.println("  list [--user USER_ID] [PACKAGE [PACKAGE [...]]]");
+        out.println("    With optional parameter PACKAGE, limit output to the specified");
+        out.println("    package. With optional parameter FIELD, limit output to");
+        out.println("    the value of that SettingsItem field. Field names are");
+        out.println("    case insensitive and out.println the m prefix can be omitted,");
+        out.println("    so the following are equivalent: mState, mstate, State, state.");
+        out.println("  list [--user USER_ID] [PACKAGE]");
         out.println("    Print information about target and overlay packages.");
         out.println("    Overlay packages are printed in priority order. With optional");
-        out.println("    parameters PACKAGEs, limit output to the specified packages");
-        out.println("    but include more information about each package.");
+        out.println("    parameter PACKAGE, limit output to the specified package.");
         out.println("  enable [--user USER_ID] PACKAGE");
         out.println("    Enable overlay package PACKAGE.");
         out.println("  disable [--user USER_ID] PACKAGE");
@@ -116,14 +120,20 @@
                     return 1;
             }
         }
+        final String packageName = getNextArg();
 
         final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId);
         for (final String targetPackageName : allOverlays.keySet()) {
-            out.println(targetPackageName);
+            if (targetPackageName.equals(packageName)) {
+                out.println(targetPackageName);
+            }
             List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName);
             final int n = overlaysForTarget.size();
             for (int i = 0; i < n; i++) {
                 final OverlayInfo oi = overlaysForTarget.get(i);
+                if (!targetPackageName.equals(packageName) && !oi.packageName.equals(packageName)) {
+                    continue;
+                }
                 String status;
                 switch (oi.state) {
                     case OverlayInfo.STATE_ENABLED_STATIC:
@@ -139,7 +149,9 @@
                 }
                 out.println(String.format("%s %s", status, oi.packageName));
             }
-            out.println();
+            if (targetPackageName.equals(packageName)) {
+                out.println();
+            }
         }
         return 0;
     }