Loading services/core/java/com/android/server/PackageWatchdog.java +81 −18 Original line number Diff line number Diff line Loading @@ -52,9 +52,7 @@ import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileNotFoundException; Loading Loading @@ -149,6 +147,7 @@ public class PackageWatchdog { private static final String ATTR_DURATION = "duration"; private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration"; private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check"; private static final String ATTR_MITIGATION_CALLS = "mitigation-calls"; @GuardedBy("PackageWatchdog.class") private static PackageWatchdog sPackageWatchdog; Loading Loading @@ -1067,6 +1066,33 @@ public class PackageWatchdog { } } /** Convert a {@code LongArrayQueue} to a String of comma-separated values. */ public static String longArrayQueueToString(LongArrayQueue queue) { if (queue.size() > 0) { StringBuilder sb = new StringBuilder(); sb.append(queue.get(0)); for (int i = 1; i < queue.size(); i++) { sb.append(","); sb.append(queue.get(i)); } return sb.toString(); } return ""; } /** Parse a comma-separated String of longs into a LongArrayQueue. */ public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) { LongArrayQueue result = new LongArrayQueue(); if (!TextUtils.isEmpty(commaSeparatedValues)) { String[] values = commaSeparatedValues.split(","); for (String value : values) { result.addLast(Long.parseLong(value)); } } return result; } /** Dump status of every observer in mAllObservers. */ public void dump(IndentingPrintWriter pw) { pw.println("Package Watchdog status"); Loading Loading @@ -1240,16 +1266,7 @@ public class PackageWatchdog { while (XmlUtils.nextElementWithin(parser, innerDepth)) { if (TAG_PACKAGE.equals(parser.getName())) { try { String packageName = parser.getAttributeValue( null, ATTR_NAME); long duration = parser.getAttributeLong( null, ATTR_DURATION); long healthCheckDuration = parser.getAttributeLong( null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION); boolean hasPassedHealthCheck = parser.getAttributeBoolean( null, ATTR_PASSED_HEALTH_CHECK, false); MonitoredPackage pkg = watchdog.newMonitoredPackage(packageName, duration, healthCheckDuration, hasPassedHealthCheck); MonitoredPackage pkg = watchdog.parseMonitoredPackage(parser); if (pkg != null) { packages.add(pkg); } Loading Loading @@ -1305,16 +1322,31 @@ public class PackageWatchdog { MonitoredPackage newMonitoredPackage( String name, long durationMs, boolean hasPassedHealthCheck) { return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck); return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck, new LongArrayQueue()); } MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck) { boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) { VersionedPackage pkg = getVersionedPackage(name); if (pkg == null) { return null; } return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs, hasPassedHealthCheck); return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs, hasPassedHealthCheck, mitigationCalls); } MonitoredPackage parseMonitoredPackage(TypedXmlPullParser parser) throws XmlPullParserException { String packageName = parser.getAttributeValue(null, ATTR_NAME); long duration = parser.getAttributeLong(null, ATTR_DURATION); long healthCheckDuration = parser.getAttributeLong(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION); boolean hasPassedHealthCheck = parser.getAttributeBoolean(null, ATTR_PASSED_HEALTH_CHECK); LongArrayQueue mitigationCalls = parseLongArrayQueue( parser.getAttributeValue(null, ATTR_MITIGATION_CALLS)); return newMonitoredPackage(packageName, duration, healthCheckDuration, hasPassedHealthCheck, mitigationCalls); } /** Loading @@ -1332,7 +1364,7 @@ public class PackageWatchdog { // Times when an observer was called to mitigate this package's failure. Sorted in // ascending order. @GuardedBy("mLock") private final LongArrayQueue mMitigationCalls = new LongArrayQueue(); private final LongArrayQueue mMitigationCalls; // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after // methods that could change the health check state: handleElapsedTimeLocked and // tryPassHealthCheckLocked Loading @@ -1353,12 +1385,14 @@ public class PackageWatchdog { @GuardedBy("mLock") private long mHealthCheckDurationMs = Long.MAX_VALUE; private MonitoredPackage(VersionedPackage pkg, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck) { MonitoredPackage(VersionedPackage pkg, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) { mPackage = pkg; mDurationMs = durationMs; mHealthCheckDurationMs = healthCheckDurationMs; mHasPassedHealthCheck = hasPassedHealthCheck; mMitigationCalls = mitigationCalls; updateHealthCheckStateLocked(); } Loading @@ -1370,6 +1404,8 @@ public class PackageWatchdog { out.attributeLong(null, ATTR_DURATION, mDurationMs); out.attributeLong(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION, mHealthCheckDurationMs); out.attributeBoolean(null, ATTR_PASSED_HEALTH_CHECK, mHasPassedHealthCheck); LongArrayQueue normalizedCalls = normalizeMitigationCalls(); out.attribute(null, ATTR_MITIGATION_CALLS, longArrayQueueToString(normalizedCalls)); out.endTag(null, TAG_PACKAGE); } Loading Loading @@ -1422,6 +1458,23 @@ public class PackageWatchdog { return mMitigationCalls.size(); } /** * Before writing to disk, make the mitigation call timestamps relative to the current * system uptime. This is because they need to be relative to the uptime which will reset * at the next boot. * * @return a LongArrayQueue of the mitigation calls relative to the current system uptime. */ @GuardedBy("mLock") public LongArrayQueue normalizeMitigationCalls() { LongArrayQueue normalized = new LongArrayQueue(); final long now = mSystemClock.uptimeMillis(); for (int i = 0; i < mMitigationCalls.size(); i++) { normalized.addLast(mMitigationCalls.get(i) - now); } return normalized; } /** * Sets the initial health check duration. * Loading Loading @@ -1582,6 +1635,16 @@ public class PackageWatchdog { private long toPositive(long value) { return value > 0 ? value : Long.MAX_VALUE; } /** Compares the equality of this object with another {@link MonitoredPackage}. */ @VisibleForTesting boolean isEqualTo(MonitoredPackage pkg) { return (getName().equals(pkg.getName())) && mDurationMs == pkg.mDurationMs && mHasPassedHealthCheck == pkg.mHasPassedHealthCheck && mHealthCheckDurationMs == pkg.mHealthCheckDurationMs && (mMitigationCalls.toString()).equals(pkg.mMitigationCalls.toString()); } } /** Loading tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +76 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; Loading @@ -43,10 +44,15 @@ import android.os.SystemProperties; import android.os.test.TestLooper; import android.provider.DeviceConfig; import android.util.AtomicFile; import android.util.LongArrayQueue; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.util.XmlUtils; import com.android.server.PackageWatchdog.HealthCheckState; import com.android.server.PackageWatchdog.MonitoredPackage; import com.android.server.PackageWatchdog.PackageHealthObserver; Loading @@ -64,6 +70,7 @@ import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; Loading Loading @@ -739,7 +746,8 @@ public class PackageWatchdogTest { false /* hasPassedHealthCheck */); MonitoredPackage m2 = wd.newMonitoredPackage(APP_B, LONG_DURATION, false); MonitoredPackage m3 = wd.newMonitoredPackage(APP_C, LONG_DURATION, false); MonitoredPackage m4 = wd.newMonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true); MonitoredPackage m4 = wd.newMonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true, new LongArrayQueue()); // Verify transition: inactive -> active -> passed // Verify initially inactive Loading Loading @@ -1210,6 +1218,73 @@ public class PackageWatchdogTest { assertThat(observer.mMitigationCounts).isEqualTo(List.of(1, 2, 3, 3, 2, 3)); } @Test public void testNormalizingMitigationCalls() { PackageWatchdog watchdog = createWatchdog(); LongArrayQueue mitigationCalls = new LongArrayQueue(); mitigationCalls.addLast(1000); mitigationCalls.addLast(2000); mitigationCalls.addLast(3000); MonitoredPackage pkg = watchdog.newMonitoredPackage( "test", 123, 456, true, mitigationCalls); // Make current system uptime 10000ms. moveTimeForwardAndDispatch(9999); LongArrayQueue expectedCalls = pkg.normalizeMitigationCalls(); assertThat(expectedCalls.size()).isEqualTo(mitigationCalls.size()); for (int i = 0; i < mitigationCalls.size(); i++) { assertThat(expectedCalls.get(i)).isEqualTo(mitigationCalls.get(i) - 10000); } } /** * Ensure that a {@link MonitoredPackage} may be correctly written and read in order to persist * across reboots. */ @Test public void testWritingAndReadingMonitoredPackage() throws Exception { PackageWatchdog watchdog = createWatchdog(); LongArrayQueue mitigationCalls = new LongArrayQueue(); mitigationCalls.addLast(1000); mitigationCalls.addLast(2000); mitigationCalls.addLast(3000); MonitoredPackage writePkg = watchdog.newMonitoredPackage( "test.package", 1000, 2000, true, mitigationCalls); // Move time forward so that the current uptime is 4000ms. Therefore, the written mitigation // calls will each be reduced by 4000. moveTimeForwardAndDispatch(3999); LongArrayQueue expectedCalls = new LongArrayQueue(); expectedCalls.addLast(-3000); expectedCalls.addLast(-2000); expectedCalls.addLast(-1000); MonitoredPackage expectedPkg = watchdog.newMonitoredPackage( "test.package", 1000, 2000, true, expectedCalls); // Write the package File tmpFile = File.createTempFile("package-watchdog-test", ".xml"); AtomicFile testFile = new AtomicFile(tmpFile); FileOutputStream stream = testFile.startWrite(); TypedXmlSerializer outputSerializer = Xml.resolveSerializer(stream); outputSerializer.startDocument(null, true); writePkg.writeLocked(outputSerializer); outputSerializer.endDocument(); testFile.finishWrite(stream); // Read the package TypedXmlPullParser parser = Xml.resolvePullParser(testFile.openRead()); XmlUtils.beginDocument(parser, "package"); MonitoredPackage readPkg = watchdog.parseMonitoredPackage(parser); assertTrue(readPkg.isEqualTo(expectedPkg)); } private void adoptShellPermissions(String... permissions) { InstrumentationRegistry .getInstrumentation() Loading Loading
services/core/java/com/android/server/PackageWatchdog.java +81 −18 Original line number Diff line number Diff line Loading @@ -52,9 +52,7 @@ import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileNotFoundException; Loading Loading @@ -149,6 +147,7 @@ public class PackageWatchdog { private static final String ATTR_DURATION = "duration"; private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration"; private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check"; private static final String ATTR_MITIGATION_CALLS = "mitigation-calls"; @GuardedBy("PackageWatchdog.class") private static PackageWatchdog sPackageWatchdog; Loading Loading @@ -1067,6 +1066,33 @@ public class PackageWatchdog { } } /** Convert a {@code LongArrayQueue} to a String of comma-separated values. */ public static String longArrayQueueToString(LongArrayQueue queue) { if (queue.size() > 0) { StringBuilder sb = new StringBuilder(); sb.append(queue.get(0)); for (int i = 1; i < queue.size(); i++) { sb.append(","); sb.append(queue.get(i)); } return sb.toString(); } return ""; } /** Parse a comma-separated String of longs into a LongArrayQueue. */ public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) { LongArrayQueue result = new LongArrayQueue(); if (!TextUtils.isEmpty(commaSeparatedValues)) { String[] values = commaSeparatedValues.split(","); for (String value : values) { result.addLast(Long.parseLong(value)); } } return result; } /** Dump status of every observer in mAllObservers. */ public void dump(IndentingPrintWriter pw) { pw.println("Package Watchdog status"); Loading Loading @@ -1240,16 +1266,7 @@ public class PackageWatchdog { while (XmlUtils.nextElementWithin(parser, innerDepth)) { if (TAG_PACKAGE.equals(parser.getName())) { try { String packageName = parser.getAttributeValue( null, ATTR_NAME); long duration = parser.getAttributeLong( null, ATTR_DURATION); long healthCheckDuration = parser.getAttributeLong( null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION); boolean hasPassedHealthCheck = parser.getAttributeBoolean( null, ATTR_PASSED_HEALTH_CHECK, false); MonitoredPackage pkg = watchdog.newMonitoredPackage(packageName, duration, healthCheckDuration, hasPassedHealthCheck); MonitoredPackage pkg = watchdog.parseMonitoredPackage(parser); if (pkg != null) { packages.add(pkg); } Loading Loading @@ -1305,16 +1322,31 @@ public class PackageWatchdog { MonitoredPackage newMonitoredPackage( String name, long durationMs, boolean hasPassedHealthCheck) { return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck); return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck, new LongArrayQueue()); } MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck) { boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) { VersionedPackage pkg = getVersionedPackage(name); if (pkg == null) { return null; } return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs, hasPassedHealthCheck); return new MonitoredPackage(pkg, durationMs, healthCheckDurationMs, hasPassedHealthCheck, mitigationCalls); } MonitoredPackage parseMonitoredPackage(TypedXmlPullParser parser) throws XmlPullParserException { String packageName = parser.getAttributeValue(null, ATTR_NAME); long duration = parser.getAttributeLong(null, ATTR_DURATION); long healthCheckDuration = parser.getAttributeLong(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION); boolean hasPassedHealthCheck = parser.getAttributeBoolean(null, ATTR_PASSED_HEALTH_CHECK); LongArrayQueue mitigationCalls = parseLongArrayQueue( parser.getAttributeValue(null, ATTR_MITIGATION_CALLS)); return newMonitoredPackage(packageName, duration, healthCheckDuration, hasPassedHealthCheck, mitigationCalls); } /** Loading @@ -1332,7 +1364,7 @@ public class PackageWatchdog { // Times when an observer was called to mitigate this package's failure. Sorted in // ascending order. @GuardedBy("mLock") private final LongArrayQueue mMitigationCalls = new LongArrayQueue(); private final LongArrayQueue mMitigationCalls; // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after // methods that could change the health check state: handleElapsedTimeLocked and // tryPassHealthCheckLocked Loading @@ -1353,12 +1385,14 @@ public class PackageWatchdog { @GuardedBy("mLock") private long mHealthCheckDurationMs = Long.MAX_VALUE; private MonitoredPackage(VersionedPackage pkg, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck) { MonitoredPackage(VersionedPackage pkg, long durationMs, long healthCheckDurationMs, boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) { mPackage = pkg; mDurationMs = durationMs; mHealthCheckDurationMs = healthCheckDurationMs; mHasPassedHealthCheck = hasPassedHealthCheck; mMitigationCalls = mitigationCalls; updateHealthCheckStateLocked(); } Loading @@ -1370,6 +1404,8 @@ public class PackageWatchdog { out.attributeLong(null, ATTR_DURATION, mDurationMs); out.attributeLong(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION, mHealthCheckDurationMs); out.attributeBoolean(null, ATTR_PASSED_HEALTH_CHECK, mHasPassedHealthCheck); LongArrayQueue normalizedCalls = normalizeMitigationCalls(); out.attribute(null, ATTR_MITIGATION_CALLS, longArrayQueueToString(normalizedCalls)); out.endTag(null, TAG_PACKAGE); } Loading Loading @@ -1422,6 +1458,23 @@ public class PackageWatchdog { return mMitigationCalls.size(); } /** * Before writing to disk, make the mitigation call timestamps relative to the current * system uptime. This is because they need to be relative to the uptime which will reset * at the next boot. * * @return a LongArrayQueue of the mitigation calls relative to the current system uptime. */ @GuardedBy("mLock") public LongArrayQueue normalizeMitigationCalls() { LongArrayQueue normalized = new LongArrayQueue(); final long now = mSystemClock.uptimeMillis(); for (int i = 0; i < mMitigationCalls.size(); i++) { normalized.addLast(mMitigationCalls.get(i) - now); } return normalized; } /** * Sets the initial health check duration. * Loading Loading @@ -1582,6 +1635,16 @@ public class PackageWatchdog { private long toPositive(long value) { return value > 0 ? value : Long.MAX_VALUE; } /** Compares the equality of this object with another {@link MonitoredPackage}. */ @VisibleForTesting boolean isEqualTo(MonitoredPackage pkg) { return (getName().equals(pkg.getName())) && mDurationMs == pkg.mDurationMs && mHasPassedHealthCheck == pkg.mHasPassedHealthCheck && mHealthCheckDurationMs == pkg.mHealthCheckDurationMs && (mMitigationCalls.toString()).equals(pkg.mMitigationCalls.toString()); } } /** Loading
tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +76 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; Loading @@ -43,10 +44,15 @@ import android.os.SystemProperties; import android.os.test.TestLooper; import android.provider.DeviceConfig; import android.util.AtomicFile; import android.util.LongArrayQueue; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.util.XmlUtils; import com.android.server.PackageWatchdog.HealthCheckState; import com.android.server.PackageWatchdog.MonitoredPackage; import com.android.server.PackageWatchdog.PackageHealthObserver; Loading @@ -64,6 +70,7 @@ import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; Loading Loading @@ -739,7 +746,8 @@ public class PackageWatchdogTest { false /* hasPassedHealthCheck */); MonitoredPackage m2 = wd.newMonitoredPackage(APP_B, LONG_DURATION, false); MonitoredPackage m3 = wd.newMonitoredPackage(APP_C, LONG_DURATION, false); MonitoredPackage m4 = wd.newMonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true); MonitoredPackage m4 = wd.newMonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true, new LongArrayQueue()); // Verify transition: inactive -> active -> passed // Verify initially inactive Loading Loading @@ -1210,6 +1218,73 @@ public class PackageWatchdogTest { assertThat(observer.mMitigationCounts).isEqualTo(List.of(1, 2, 3, 3, 2, 3)); } @Test public void testNormalizingMitigationCalls() { PackageWatchdog watchdog = createWatchdog(); LongArrayQueue mitigationCalls = new LongArrayQueue(); mitigationCalls.addLast(1000); mitigationCalls.addLast(2000); mitigationCalls.addLast(3000); MonitoredPackage pkg = watchdog.newMonitoredPackage( "test", 123, 456, true, mitigationCalls); // Make current system uptime 10000ms. moveTimeForwardAndDispatch(9999); LongArrayQueue expectedCalls = pkg.normalizeMitigationCalls(); assertThat(expectedCalls.size()).isEqualTo(mitigationCalls.size()); for (int i = 0; i < mitigationCalls.size(); i++) { assertThat(expectedCalls.get(i)).isEqualTo(mitigationCalls.get(i) - 10000); } } /** * Ensure that a {@link MonitoredPackage} may be correctly written and read in order to persist * across reboots. */ @Test public void testWritingAndReadingMonitoredPackage() throws Exception { PackageWatchdog watchdog = createWatchdog(); LongArrayQueue mitigationCalls = new LongArrayQueue(); mitigationCalls.addLast(1000); mitigationCalls.addLast(2000); mitigationCalls.addLast(3000); MonitoredPackage writePkg = watchdog.newMonitoredPackage( "test.package", 1000, 2000, true, mitigationCalls); // Move time forward so that the current uptime is 4000ms. Therefore, the written mitigation // calls will each be reduced by 4000. moveTimeForwardAndDispatch(3999); LongArrayQueue expectedCalls = new LongArrayQueue(); expectedCalls.addLast(-3000); expectedCalls.addLast(-2000); expectedCalls.addLast(-1000); MonitoredPackage expectedPkg = watchdog.newMonitoredPackage( "test.package", 1000, 2000, true, expectedCalls); // Write the package File tmpFile = File.createTempFile("package-watchdog-test", ".xml"); AtomicFile testFile = new AtomicFile(tmpFile); FileOutputStream stream = testFile.startWrite(); TypedXmlSerializer outputSerializer = Xml.resolveSerializer(stream); outputSerializer.startDocument(null, true); writePkg.writeLocked(outputSerializer); outputSerializer.endDocument(); testFile.finishWrite(stream); // Read the package TypedXmlPullParser parser = Xml.resolvePullParser(testFile.openRead()); XmlUtils.beginDocument(parser, "package"); MonitoredPackage readPkg = watchdog.parseMonitoredPackage(parser); assertTrue(readPkg.isEqualTo(expectedPkg)); } private void adoptShellPermissions(String... permissions) { InstrumentationRegistry .getInstrumentation() Loading