Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5c9af88b authored by Shrinidhi Hegde's avatar Shrinidhi Hegde
Browse files

Add kill switch for high impact rollbacks.

Test: unit test
Bug: 291137901
Change-Id: I8cd6b756b446fbeab782dcd78baac42326d8d7dd
parent ea0c498c
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -75,6 +75,9 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
    private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
            | ApplicationInfo.FLAG_SYSTEM;

    private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
            "persist.device_config.configuration.disable_high_impact_rollback";

    private final Context mContext;
    private final Handler mHandler;
    private final ApexManager mApexManager;
@@ -605,6 +608,10 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
            // Apply all available low impact rollbacks.
            mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
        } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
            // Check disable_high_impact_rollback device config before performing rollback
            if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
                return;
            }
            // Rollback one package at a time. If that doesn't resolve the issue, rollback
            // next with same impact level.
            mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
@@ -718,7 +725,9 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
                break;
            case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
                if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
                    impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
                }
                break;
            default:
                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+101 −1
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -42,6 +44,7 @@ import android.content.rollback.RollbackManager;
import android.crashrecovery.flags.Flags;
import android.os.Handler;
import android.os.MessageQueue;
import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;

import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +68,7 @@ import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -90,7 +94,7 @@ public class RollbackPackageHealthObserverTest {

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    private HashMap<String, String> mSystemSettingsMap;
    private MockitoSession mSession;
    private static final String APP_A = "com.package.a";
    private static final String APP_B = "com.package.b";
@@ -99,6 +103,9 @@ public class RollbackPackageHealthObserverTest {
    private static final long VERSION_CODE_2 = 2L;
    private static final String LOG_TAG = "RollbackPackageHealthObserverTest";

    private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
            "persist.device_config.configuration.disable_high_impact_rollback";

    private SystemConfig mSysConfig;

    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -111,11 +118,34 @@ public class RollbackPackageHealthObserverTest {
                .initMocks(this)
                .strictness(Strictness.LENIENT)
                .spyStatic(PackageWatchdog.class)
                .spyStatic(SystemProperties.class)
                .startMocking();
        mSystemSettingsMap = new HashMap<>();

        // Mock PackageWatchdog
        doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
                .when(() -> PackageWatchdog.getInstance(mMockContext));

        // Mock SystemProperties setter and various getters
        doAnswer((Answer<Void>) invocationOnMock -> {
                    String key = invocationOnMock.getArgument(0);
                    String value = invocationOnMock.getArgument(1);

                    mSystemSettingsMap.put(key, value);
                    return null;
                }
        ).when(() -> SystemProperties.set(anyString(), anyString()));

        doAnswer((Answer<Boolean>) invocationOnMock -> {
                    String key = invocationOnMock.getArgument(0);
                    boolean defaultValue = invocationOnMock.getArgument(1);

                    String storedValue = mSystemSettingsMap.get(key);
                    return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
                }
        ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));

        SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(false));
    }

    @After
@@ -609,6 +639,32 @@ public class RollbackPackageHealthObserverTest {
                observer.onBootLoop(1));
    }

    @Test
    public void onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage()
            throws PackageManager.NameNotFoundException {
        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
        SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
        VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
        VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
        PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
                null, null, false, false,
                null);
        RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
                false, null, 111,
                PackageManager.ROLLBACK_USER_IMPACT_HIGH);
        RollbackPackageHealthObserver observer =
                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));

        when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
        // Make the rollbacks available
        when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
        when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);

        assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
                observer.onBootLoop(1));
    }

    /**
     * When the rollback impact level is manual only return user impact level 0. (User impact level
     * 0 is ignored by package watchdog)
@@ -924,6 +980,50 @@ public class RollbackPackageHealthObserverTest {
        assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
    }

    /**
     * Don't roll back if kill switch is enabled.
     */
    @Test
    public void executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh()
            throws PackageManager.NameNotFoundException {
        mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
        SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
        int rollbackId1 = 1;
        VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
        VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
        PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
                null, null , false, false,
                null);
        RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
                false, null, 111,
                PackageManager.ROLLBACK_USER_IMPACT_HIGH);
        int rollbackId2 = 2;
        VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
        VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
        PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
                null, null , false, false,
                null);
        RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
                false, null, 111,
                PackageManager.ROLLBACK_USER_IMPACT_HIGH);
        RollbackPackageHealthObserver observer =
                spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
        ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);

        when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
        // Make the rollbacks available
        when(mRollbackManager.getAvailableRollbacks()).thenReturn(
                List.of(rollbackInfo1, rollbackInfo2));
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
        when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);

        observer.executeBootLoopMitigation(1);
        waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));

        verify(mRollbackManager, never()).commitRollback(
                argument.capture(), any(), any());
    }

    private void waitForIdleHandler(Handler handler, Duration timeout) {
        final MessageQueue queue = handler.getLooper().getQueue();
        final CountDownLatch latch = new CountDownLatch(1);