Merge "Add dumpsys support to RulesManagerService"

This commit is contained in:
Neil Fuller
2017-07-03 09:29:32 +00:00
committed by Gerrit Code Review
9 changed files with 314 additions and 7 deletions

View File

@@ -125,4 +125,8 @@ public final class DistroRulesVersion implements Parcelable {
+ ", mRevision='" + mRevision + '\''
+ '}';
}
public String toDumpString() {
return mRulesVersion + "," + mRevision;
}
}

View File

@@ -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());
}
}

View File

@@ -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 +
'}';
}
}

View File

@@ -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);
}

View File

@@ -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";
}
}
}

View File

@@ -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) {

View File

@@ -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());
}
}

View File

@@ -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);

View File

@@ -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);