OMS: add 'cmd overlay lookup' command

Add a shell command to look up the value of any resource. This allows a
developer to quickly see what the value of a resource is, using the
current device configuration and the currently enabled overlays. For
more fine-grained control, use 'idmap2 lookup' instead.

Example:

  $ adb shell cmd overlay lookup --verbose android android:bool/config_annoy_dianne
  Resolution for 0x01110020 android:bool/config_annoy_dianne
          For config -en-rUS-ldltr-sw320dp-w320dp-h461dp-normal-notlong-notround-nowidecg-lowdr-port-uiModeType=2-notnight-hdpi-finger-keysexposed-qwerty-trackball-728x480-v29
          Found initial: android
  true

  $ adb shell cmd overlay enable test.overlay

  $ adb shell cmd overlay lookup --verbose android android:bool/config_annoy_dianne
  Resolution for 0x01110020 test.overlay:bool/config_annoy_dianne
          For config -en-rUS-ldltr-sw320dp-w320dp-h461dp-normal-notlong-notround-nowidecg-lowdr-port-uiModeType=2-notnight-hdpi-finger-keysexposed-qwerty-trackball-728x480-v29
          Found initial: android
          Overlaid: test.overlay
  false

Test: manual: adb shell cmd overlay lookup --verbose android android:array/preloaded_drawables
Change-Id: I00efe23b1a0f7342a5cf7f4ba86f3eae6714c9aa
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 965ddc9..f8b3fb2 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -744,7 +744,7 @@
                 @NonNull final FileDescriptor out, @NonNull final FileDescriptor err,
                 @NonNull final String[] args, @NonNull final ShellCallback callback,
                 @NonNull final ResultReceiver resultReceiver) {
-            (new OverlayManagerShellCommand(this)).exec(
+            (new OverlayManagerShellCommand(getContext(), this)).exec(
                     this, in, out, err, args, callback, resultReceiver);
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 99f5839..eb43275 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -18,15 +18,23 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.util.TypedValue;
 
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Implementation of 'cmd overlay' commands.
@@ -36,9 +44,11 @@
  * for a list of available commands.
  */
 final class OverlayManagerShellCommand extends ShellCommand {
+    private final Context mContext;
     private final IOverlayManager mInterface;
 
-    OverlayManagerShellCommand(@NonNull final IOverlayManager iom) {
+    OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
+        mContext = ctx;
         mInterface = iom;
     }
 
@@ -60,6 +70,8 @@
                     return runEnableExclusive();
                 case "set-priority":
                     return runSetPriority();
+                case "lookup":
+                    return runLookup();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -102,6 +114,10 @@
         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
         out.println("    If PARENT is the special keyword 'highest', change priority of");
         out.println("    PACKAGE to the highest priority.");
+        out.println("  lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
+        out.println("    Load a package and print the value of a given resource");
+        out.println("    applying the current configuration and enabled overlays.");
+        out.println("    For a more fine-grained alernative, use 'idmap2 lookup'.");
     }
 
     private int runList() throws RemoteException {
@@ -253,4 +269,92 @@
             return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1;
         }
     }
+
+    private int runLookup() throws RemoteException {
+        final PrintWriter out = getOutPrintWriter();
+        final PrintWriter err = getErrPrintWriter();
+
+        final boolean verbose = "--verbose".equals(getNextOption());
+
+        final String packageToLoad = getNextArgRequired();
+
+        final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name
+        final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)");
+        final Matcher matcher = regex.matcher(fullyQualifiedResourceName);
+        if (!matcher.matches()) {
+            err.println("Error: bad resource name, doesn't match package:type/name");
+            return 1;
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            err.println("Error: failed to get package manager");
+            return 1;
+        }
+
+        final Resources res;
+        try {
+            res = pm.getResourcesForApplication(packageToLoad);
+        } catch (PackageManager.NameNotFoundException e) {
+            err.println("Error: failed to get resources for package " + packageToLoad);
+            return 1;
+        }
+        final AssetManager assets = res.getAssets();
+        try {
+            assets.setResourceResolutionLoggingEnabled(true);
+
+            // first try as non-complex type ...
+            try {
+                final TypedValue value = new TypedValue();
+                res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */);
+                final CharSequence valueString = value.coerceToString();
+                final String resolution = assets.getLastResourceResolution();
+
+                res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */);
+                final CharSequence resolvedString = value.coerceToString();
+
+                if (verbose) {
+                    out.println(resolution);
+                }
+
+                if (valueString.equals(resolvedString)) {
+                    out.println(valueString);
+                } else {
+                    out.println(valueString + " -> " + resolvedString);
+                }
+                return 0;
+            } catch (Resources.NotFoundException e) {
+                // this is ok, resource could still be a complex type
+            }
+
+            // ... then try as complex type
+            try {
+
+                final String pkg = matcher.group(1);
+                final String type = matcher.group(2);
+                final String name = matcher.group(3);
+                final int resid = res.getIdentifier(name, type, pkg);
+                if (resid == 0) {
+                    throw new Resources.NotFoundException();
+                }
+                final TypedArray array = res.obtainTypedArray(resid);
+                if (verbose) {
+                    out.println(assets.getLastResourceResolution());
+                }
+                TypedValue tv = new TypedValue();
+                for (int i = 0; i < array.length(); i++) {
+                    array.getValue(i, tv);
+                    out.println(tv.coerceToString());
+                }
+                array.recycle();
+                return 0;
+            } catch (Resources.NotFoundException e) {
+                // give up
+                err.println("Error: failed to get the resource " + fullyQualifiedResourceName);
+                return 1;
+            }
+        } finally {
+            assets.setResourceResolutionLoggingEnabled(false);
+        }
+    }
 }