Merge "Add dumpsys support to RulesManagerService"
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