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
This commit is contained in:
@@ -125,4 +125,8 @@ public final class DistroRulesVersion implements Parcelable {
|
||||
+ ", mRevision='" + mRevision + '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
public String toDumpString() {
|
||||
return mRulesVersion + "," + mRevision;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.ParseException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE;
|
||||
import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS;
|
||||
@@ -375,4 +376,8 @@ final class PackageStatusStorage {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void dump(PrintWriter printWriter) {
|
||||
printWriter.println("Package status: " + getPackageStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.provider.TimeZoneRulesDataContract;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Monitors the installed applications associated with time zone updates. If the app packages are
|
||||
@@ -510,4 +511,23 @@ public class PackageTracker implements IntentHelper.Listener {
|
||||
Slog.wtf(TAG, message, cause);
|
||||
throw new RuntimeException(message, cause);
|
||||
}
|
||||
|
||||
public void dump(PrintWriter fout) {
|
||||
fout.println("PackageTrackerState: " + toString());
|
||||
mPackageStatusStorage.dump(fout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PackageTracker{" +
|
||||
"mTrackingEnabled=" + mTrackingEnabled +
|
||||
", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' +
|
||||
", mDataAppPackageName='" + mDataAppPackageName + '\'' +
|
||||
", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis +
|
||||
", mFailedCheckRetryCount=" + mFailedCheckRetryCount +
|
||||
", mLastTriggerTimestamp=" + mLastTriggerTimestamp +
|
||||
", mCheckTriggered=" + mCheckTriggered +
|
||||
", mCheckFailureCount=" + mCheckFailureCount +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
|
||||
package com.android.server.timezone;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* An easy-to-mock interface around permission checks for use by {@link RulesManagerService}.
|
||||
*/
|
||||
public interface PermissionHelper {
|
||||
|
||||
void enforceCallerHasPermission(String requiredPermission) throws SecurityException;
|
||||
|
||||
boolean checkDumpPermission(String tag, PrintWriter printWriter);
|
||||
}
|
||||
|
||||
@@ -37,12 +37,24 @@ import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import libcore.icu.ICU;
|
||||
import libcore.util.ZoneInfoDB;
|
||||
|
||||
import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
|
||||
import static android.app.timezone.RulesState.DISTRO_STATUS_NONE;
|
||||
import static android.app.timezone.RulesState.DISTRO_STATUS_UNKNOWN;
|
||||
import static android.app.timezone.RulesState.STAGED_OPERATION_INSTALL;
|
||||
import static android.app.timezone.RulesState.STAGED_OPERATION_NONE;
|
||||
import static android.app.timezone.RulesState.STAGED_OPERATION_UNINSTALL;
|
||||
import static android.app.timezone.RulesState.STAGED_OPERATION_UNKNOWN;
|
||||
|
||||
// TODO(nfuller) Add EventLog calls where useful in the system server.
|
||||
// TODO(nfuller) Check logging best practices in the system server.
|
||||
@@ -113,6 +125,11 @@ public final class RulesManagerService extends IRulesManager.Stub {
|
||||
public RulesState getRulesState() {
|
||||
mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
|
||||
|
||||
return getRulesStateInternal();
|
||||
}
|
||||
|
||||
/** Like {@link #getRulesState()} without the permission check. */
|
||||
private RulesState getRulesStateInternal() {
|
||||
synchronized(this) {
|
||||
String systemRulesVersion;
|
||||
try {
|
||||
@@ -126,18 +143,18 @@ public final class RulesManagerService extends IRulesManager.Stub {
|
||||
|
||||
// Determine the staged operation status, if possible.
|
||||
DistroRulesVersion stagedDistroRulesVersion = null;
|
||||
int stagedOperationStatus = RulesState.STAGED_OPERATION_UNKNOWN;
|
||||
int stagedOperationStatus = STAGED_OPERATION_UNKNOWN;
|
||||
if (!operationInProgress) {
|
||||
StagedDistroOperation stagedDistroOperation;
|
||||
try {
|
||||
stagedDistroOperation = mInstaller.getStagedDistroOperation();
|
||||
if (stagedDistroOperation == null) {
|
||||
stagedOperationStatus = RulesState.STAGED_OPERATION_NONE;
|
||||
stagedOperationStatus = STAGED_OPERATION_NONE;
|
||||
} else if (stagedDistroOperation.isUninstall) {
|
||||
stagedOperationStatus = RulesState.STAGED_OPERATION_UNINSTALL;
|
||||
stagedOperationStatus = STAGED_OPERATION_UNINSTALL;
|
||||
} else {
|
||||
// Must be an install.
|
||||
stagedOperationStatus = RulesState.STAGED_OPERATION_INSTALL;
|
||||
stagedOperationStatus = STAGED_OPERATION_INSTALL;
|
||||
DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
|
||||
stagedDistroRulesVersion = new DistroRulesVersion(
|
||||
stagedDistroVersion.rulesVersion,
|
||||
@@ -150,16 +167,16 @@ public final class RulesManagerService extends IRulesManager.Stub {
|
||||
|
||||
// Determine the installed distro state, if possible.
|
||||
DistroVersion installedDistroVersion;
|
||||
int distroStatus = RulesState.DISTRO_STATUS_UNKNOWN;
|
||||
int distroStatus = DISTRO_STATUS_UNKNOWN;
|
||||
DistroRulesVersion installedDistroRulesVersion = null;
|
||||
if (!operationInProgress) {
|
||||
try {
|
||||
installedDistroVersion = mInstaller.getInstalledDistroVersion();
|
||||
if (installedDistroVersion == null) {
|
||||
distroStatus = RulesState.DISTRO_STATUS_NONE;
|
||||
distroStatus = DISTRO_STATUS_NONE;
|
||||
installedDistroRulesVersion = null;
|
||||
} else {
|
||||
distroStatus = RulesState.DISTRO_STATUS_INSTALLED;
|
||||
distroStatus = DISTRO_STATUS_INSTALLED;
|
||||
installedDistroRulesVersion = new DistroRulesVersion(
|
||||
installedDistroVersion.rulesVersion,
|
||||
installedDistroVersion.revision);
|
||||
@@ -358,6 +375,87 @@ public final class RulesManagerService extends IRulesManager.Stub {
|
||||
mPackageTracker.recordCheckResult(checkToken, success);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
if (!mPermissionHelper.checkDumpPermission(TAG, pw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RulesState rulesState = getRulesStateInternal();
|
||||
if (args != null && args.length == 2) {
|
||||
// Formatting options used for automated tests. The format is less free-form than
|
||||
// the -format options, which are intended to be easier to parse.
|
||||
if ("-format_state".equals(args[0]) && args[1] != null) {
|
||||
for (char c : args[1].toCharArray()) {
|
||||
switch (c) {
|
||||
case 'p': // Report operation in progress
|
||||
pw.println("Operation in progress: "
|
||||
+ rulesState.isOperationInProgress());
|
||||
break;
|
||||
case 's': // Report system image rules version
|
||||
pw.println("System rules version: "
|
||||
+ rulesState.getSystemRulesVersion());
|
||||
break;
|
||||
case 'c': // Report current installation state
|
||||
pw.println("Current install state: "
|
||||
+ distroStatusToString(rulesState.getDistroStatus()));
|
||||
break;
|
||||
case 'i': // Report currently installed version
|
||||
DistroRulesVersion installedRulesVersion =
|
||||
rulesState.getInstalledDistroRulesVersion();
|
||||
pw.print("Installed rules version: ");
|
||||
if (installedRulesVersion == null) {
|
||||
pw.println("<None>");
|
||||
} else {
|
||||
pw.println(installedRulesVersion.toDumpString());
|
||||
}
|
||||
break;
|
||||
case 'o': // Report staged operation type
|
||||
int stagedOperationType = rulesState.getStagedOperationType();
|
||||
pw.println("Staged operation: "
|
||||
+ stagedOperationToString(stagedOperationType));
|
||||
break;
|
||||
case 't':
|
||||
// Report staged version (i.e. the one that will be installed next boot
|
||||
// if the staged operation is an install).
|
||||
pw.print("Staged rules version: ");
|
||||
DistroRulesVersion stagedDistroRulesVersion =
|
||||
rulesState.getStagedDistroRulesVersion();
|
||||
if (stagedDistroRulesVersion == null) {
|
||||
pw.println("<None>");
|
||||
} else {
|
||||
pw.println("Staged install version: "
|
||||
+ stagedDistroRulesVersion.toDumpString());
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
// Report the active rules version (i.e. the rules in use by the current
|
||||
// process).
|
||||
pw.println("Active rules version (ICU, libcore): "
|
||||
+ ICU.getTZDataVersion() + ","
|
||||
+ ZoneInfoDB.getInstance().getVersion());
|
||||
break;
|
||||
default:
|
||||
pw.println("Unknown option: " + c);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pw.println("RulesManagerService state: " + toString());
|
||||
pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + ","
|
||||
+ ZoneInfoDB.getInstance().getVersion());
|
||||
mPackageTracker.dump(pw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RulesManagerService{" +
|
||||
"mOperationInProgress=" + mOperationInProgress +
|
||||
'}';
|
||||
}
|
||||
|
||||
private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
|
||||
CheckToken checkToken;
|
||||
try {
|
||||
@@ -368,4 +466,30 @@ public final class RulesManagerService extends IRulesManager.Stub {
|
||||
}
|
||||
return checkToken;
|
||||
}
|
||||
|
||||
private static String distroStatusToString(int distroStatus) {
|
||||
switch(distroStatus) {
|
||||
case DISTRO_STATUS_NONE:
|
||||
return "None";
|
||||
case DISTRO_STATUS_INSTALLED:
|
||||
return "Installed";
|
||||
case DISTRO_STATUS_UNKNOWN:
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private static String stagedOperationToString(int stagedOperationType) {
|
||||
switch(stagedOperationType) {
|
||||
case STAGED_OPERATION_NONE:
|
||||
return "None";
|
||||
case STAGED_OPERATION_UNINSTALL:
|
||||
return "Uninstall";
|
||||
case STAGED_OPERATION_INSTALL:
|
||||
return "Install";
|
||||
case STAGED_OPERATION_UNKNOWN:
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
package com.android.server.timezone;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.concurrent.Executor;
|
||||
import libcore.io.Streams;
|
||||
|
||||
@@ -40,6 +43,19 @@ final class RulesManagerServiceHelperImpl implements PermissionHelper, Executor
|
||||
mContext.enforceCallingPermission(requiredPermission, null /* message */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkDumpPermission(String tag, PrintWriter pw) {
|
||||
// TODO(nfuller): Switch to DumpUtils.checkDumpPermission() when it is available in AOSP.
|
||||
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
pw.println("Permission Denial: can't dump LocationManagerService from from pid="
|
||||
+ Binder.getCallingPid()
|
||||
+ ", uid=" + Binder.getCallingUid());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO Wake lock required?
|
||||
@Override
|
||||
public void execute(Runnable runnable) {
|
||||
|
||||
@@ -25,6 +25,8 @@ import android.support.test.InstrumentationRegistry;
|
||||
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 @@ public class PackageStatusStorageTest {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ import android.provider.TimeZoneRulesDataContract;
|
||||
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 @@ public class PackageTrackerTest {
|
||||
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);
|
||||
|
||||
|
||||
@@ -32,11 +32,15 @@ import android.app.timezone.RulesState;
|
||||
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.mock;
|
||||
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 @@ public class RulesManagerServiceTest {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user