Add dumpsys support to RulesManagerService

Override the dump method so RuleManagerService can dump its state
into logs. Crude argument support has been added for dumping
specific fields in an easy to process way (for test scripts to use).

Tested with:
make -j30 FrameworksServicesTests
adb install -r -g \
  "out/target/product/angler/data/app/FrameworksServicesTests/FrameworksServicesTests.apk"
adb shell am instrument -e package com.android.server.timezone -w \
  com.android.frameworks.servicestests \
  "com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner"

Test: See above.
Test: Manual; adb shell dumpsys timezone [-format_state piscotz]
Bug: 31008728
Change-Id: I0ad83aa245232ed0b983ceacd8accfb876824d6f
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
index dd56072..b57cac0 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
@@ -25,6 +25,8 @@
 import android.support.test.filters.SmallTest;
 
 import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 
 import static junit.framework.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
@@ -228,4 +230,27 @@
         assertFalse(writeOk2);
         assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
     }
+
+    @Test
+    public void dump() {
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        // Dump initial state.
+        mPackageStatusStorage.dump(printWriter);
+
+        // No crash and it does something.
+        assertFalse(stringWriter.toString().isEmpty());
+
+        // Reset
+        stringWriter.getBuffer().setLength(0);
+        assertTrue(stringWriter.toString().isEmpty());
+
+        // Store something.
+        mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+        mPackageStatusStorage.dump(printWriter);
+
+        assertFalse(stringWriter.toString().isEmpty());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
index 4c7680b..a972e4f 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -30,6 +30,9 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -1174,6 +1177,16 @@
         assertFalse(token1.equals(token2));
     }
 
+    @Test
+    public void dump() {
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        mPackageTracker.dump(printWriter);
+
+        assertFalse(stringWriter.toString().isEmpty());
+    }
+
     private void simulatePackageInstallation(PackageVersions packageVersions) throws Exception {
         configureApplicationsValidManifests(packageVersions);
 
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
index 1407e26..2887e3b 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -32,11 +32,15 @@
 import android.os.ParcelFileDescriptor;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 import javax.annotation.Nullable;
 
+import libcore.io.IoUtils;
+
 import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -52,6 +56,7 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -724,6 +729,97 @@
         verifyPackageTrackerCalled(null /* token */, true /* success */);
     }
 
+    @Test
+    public void dump_noPermission() throws Exception {
+        when(mMockPermissionHelper.checkDumpPermission(any(String.class), any(PrintWriter.class)))
+                .thenReturn(false);
+
+        doDumpCallAndCapture(mRulesManagerService, null);
+        verifyZeroInteractions(mMockPackageTracker, mMockTimeZoneDistroInstaller);
+    }
+
+    @Test
+    public void dump_emptyArgs() throws Exception {
+        doSuccessfulDumpCall(mRulesManagerService, new String[0]);
+
+        // Verify the package tracker was consulted.
+        verify(mMockPackageTracker).dump(any(PrintWriter.class));
+    }
+
+    @Test
+    public void dump_nullArgs() throws Exception {
+        doSuccessfulDumpCall(mRulesManagerService, null);
+        // Verify the package tracker was consulted.
+        verify(mMockPackageTracker).dump(any(PrintWriter.class));
+    }
+
+    @Test
+    public void dump_unknownArgs() throws Exception {
+        String dumpedTextUnknownArgs = doSuccessfulDumpCall(
+                mRulesManagerService, new String[] { "foo", "bar"});
+
+        // Verify the package tracker was consulted.
+        verify(mMockPackageTracker).dump(any(PrintWriter.class));
+
+        String dumpedTextZeroArgs = doSuccessfulDumpCall(mRulesManagerService, null);
+        assertEquals(dumpedTextZeroArgs, dumpedTextUnknownArgs);
+    }
+
+    @Test
+    public void dump_formatState() throws Exception {
+        // Just expect these to not throw exceptions, not return nothing, and not interact with the
+        // package tracker.
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("p"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("s"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("c"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("i"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("o"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("t"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("a"));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("z" /* Unknown */));
+        doSuccessfulDumpCall(mRulesManagerService, dumpFormatArgs("piscotz"));
+
+        verifyZeroInteractions(mMockPackageTracker);
+    }
+
+    private static String[] dumpFormatArgs(String argsString) {
+        return new String[] { "-format_state", argsString};
+    }
+
+    private String doSuccessfulDumpCall(RulesManagerService rulesManagerService, String[] args)
+            throws Exception {
+        when(mMockPermissionHelper.checkDumpPermission(any(String.class), any(PrintWriter.class)))
+                .thenReturn(true);
+
+        // Set up the mocks to return (arbitrary) information about the current device state.
+        when(mMockTimeZoneDistroInstaller.getSystemRulesVersion()).thenReturn("2017a");
+        when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion()).thenReturn(
+                new DistroVersion(2, 3, "2017b", 4));
+        when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()).thenReturn(
+                StagedDistroOperation.install(new DistroVersion(5, 6, "2017c", 7)));
+
+        // Do the dump call.
+        String dumpedOutput = doDumpCallAndCapture(rulesManagerService, args);
+
+        assertFalse(dumpedOutput.isEmpty());
+
+        return dumpedOutput;
+    }
+
+    private static String doDumpCallAndCapture(
+            RulesManagerService rulesManagerService, String[] args) throws IOException {
+        File file = File.createTempFile("dump", null);
+        try {
+            try (FileOutputStream fos = new FileOutputStream(file)) {
+                FileDescriptor fd = fos.getFD();
+                rulesManagerService.dump(fd, args);
+            }
+            return IoUtils.readFileAsString(file.getAbsolutePath());
+        } finally {
+            file.delete();
+        }
+    }
+
     private void verifyNoPackageTrackerCallsMade() {
         verifyNoMoreInteractions(mMockPackageTracker);
         reset(mMockPackageTracker);