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

Commit bc074017 authored by Satoshi Niwa's avatar Satoshi Niwa
Browse files

Refine backup eligibility for non-main HSUM users

This change restricts TelephonyProvider backup eligibility to only
the main user in an HSUM environment.
BackupEligibilityRules now uses a new helper method to dynamically
select the correct system package allowlist. This differentiates
between the HSUM main user, profile users, and other non-system users,
ensuring TelephonyProvider is excluded for non-main HSUM users.

Added unit tests to confirm behavior for HSUM main/non-main users.

Flag: android.multiuser.backup_activated_for_all_users
Bug: 417649298
Bug: 406114361
Test: atest BackupEligibilityRulesTest
Change-Id: Idb1d83ef677b8422a02b38d291fb7fa7e56ffe0c
parent 160e1c04
Loading
Loading
Loading
Loading
+41 −20
Original line number Diff line number Diff line
@@ -76,16 +76,18 @@ public class BackupEligibilityRules {
            systemPackagesAllowedForProfileUser,
            Sets.newArraySet(WALLPAPER_PACKAGE, SETTINGS_PACKAGE));

    static {
        if (UserManager.isHeadlessSystemUserMode()) {
            systemPackagesAllowedForNonSystemUsers.add(TELEPHONY_PROVIDER_PACKAGE);
        }
    }
    /**
     * List of system packages that are eligible for backup for the main user in Headless System
     * User Mode (HSUM). In HSUM, certain packages are only backed up for the main user.
     */
    private static final Set<String> systemPackagesAllowedForHsumMainUser = SetUtils.union(
            systemPackagesAllowedForNonSystemUsers,
            Sets.newArraySet(TELEPHONY_PROVIDER_PACKAGE));

    private final PackageManager mPackageManager;
    private final PackageManagerInternal mPackageManagerInternal;
    private final UserManager mUserManager;
    private final int mUserId;
    private boolean mIsProfileUser = false;
    @BackupDestination  private final int mBackupDestination;
    private final boolean mSkipRestoreForLaunchedApps;

@@ -134,8 +136,7 @@ public class BackupEligibilityRules {
        mPackageManagerInternal = packageManagerInternal;
        mUserId = userId;
        mBackupDestination = backupDestination;
        UserManager userManager = context.getSystemService(UserManager.class);
        mIsProfileUser = userManager.isProfile();
        mUserManager = context.getSystemService(UserManager.class);
        mSkipRestoreForLaunchedApps = skipRestoreForLaunchedApps;
    }

@@ -167,20 +168,13 @@ public class BackupEligibilityRules {

        // 2. they run as a system-level uid
        if (UserHandle.isCore(app.uid)) {
            // and the backup is happening for a non-system user or profile on a package that is
            // not explicitly allowed.
            if (mUserId != UserHandle.USER_SYSTEM) {
                if (mIsProfileUser && !systemPackagesAllowedForProfileUser.contains(
                        app.packageName)) {
            // System apps are additionally checked:
            // ...if not allowed for the current user (governed by user-specific allowlists)
            if (!isSystemPackageAllowedForCurrentUser(app.packageName)) {
                return false;
            }
                if (!mIsProfileUser && !systemPackagesAllowedForNonSystemUsers.contains(
                        app.packageName)) {
                    return false;
                }
            }

            // or do not supply their own backup agent
            // ...or do not supply their own backup agent
            if (app.backupAgentName == null) {
                return false;
            }
@@ -199,6 +193,33 @@ public class BackupEligibilityRules {
        return !appIsDisabled(app);
    }

    /**
     * Checks if a given system package is allowed for backup for the current user.
     * True for system user ({@link android.os.UserHandle#USER_SYSTEM}); for others,
     * eligibility depends on user type (profile, HSUM main, etc.) and specific allowlists.
     */
    @SuppressWarnings("AndroidFrameworkRequiresPermission")
    private boolean isSystemPackageAllowedForCurrentUser(String packageName) {
        if (mUserId == UserHandle.USER_SYSTEM) {
            return true;
        }

        if (mUserManager.isProfile()) {
            return systemPackagesAllowedForProfileUser.contains(packageName);
        }

        // In Headless System User Mode, certain packages are only backed up for the main user.
        if (UserManager.isHeadlessSystemUserMode()) {
            UserHandle mainUser = mUserManager.getMainUser();
            if (mainUser != null && mainUser.getIdentifier() == mUserId) {
                return systemPackagesAllowedForHsumMainUser.contains(packageName);
            }
        }

        // Default allowlist for other non-system users (including non-main users in HSUM).
        return systemPackagesAllowedForNonSystemUsers.contains(packageName);
    }

    /**
    * Check if this app allows backup. Apps can opt out of backup by stating
    * android:allowBackup="false" in their manifest. However, this flag is ignored for non-system
+61 −3
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.backup.utils;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.eq;
@@ -42,6 +44,7 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.backup.UserBackupManagerService;

import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -53,7 +56,7 @@ import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;

@SmallTest
@Presubmit
@@ -66,10 +69,16 @@ public class BackupEligibilityRulesTest {
    private static final Signature SIGNATURE_2 = generateSignature((byte) 2);
    private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
    private static final Signature SIGNATURE_4 = generateSignature((byte) 4);

    private static final int NON_SYSTEM_USER_ID = 10;
    private static final UserHandle NON_SYSTEM_USER = UserHandle.of(NON_SYSTEM_USER_ID);

    @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();

    @Rule
    public final ExtendedMockitoRule extendedMockitoRule = new ExtendedMockitoRule.Builder(this)
            .setStrictness(Strictness.LENIENT).spyStatic(UserManager.class).build();

    @Mock private PackageManagerInternal mMockPackageManagerInternal;
    @Mock private PackageManager mPackageManager;
    @Mock private Context mContext;
@@ -80,9 +89,8 @@ public class BackupEligibilityRulesTest {

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mUserId = UserHandle.USER_SYSTEM;
        mockHeadlessSystemUserMode(false);
        mockContextForFullUser();
        mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);
    }
@@ -100,6 +108,7 @@ public class BackupEligibilityRulesTest {
    @Test
    public void appIsEligibleForBackup_systemUid_nonSystemUser_notAllowedPackage_returnsFalse()
            throws Exception {
        mockHeadlessSystemUserMode(false);
        setUpForNonSystemUser();

        ApplicationInfo applicationInfo =
@@ -113,6 +122,7 @@ public class BackupEligibilityRulesTest {
    @Test
    public void appIsEligibleForBackup_systemUid_nonSystemUser_allowedPackage_returnsTrue()
            throws Exception {
        mockHeadlessSystemUserMode(false);
        setUpForNonSystemUser();

        ApplicationInfo applicationInfo = getApplicationInfo(
@@ -124,6 +134,50 @@ public class BackupEligibilityRulesTest {
        assertThat(isEligible).isTrue();
    }

    @Test
    public void appIsEligibleForBackup_systemUid_hsumMainUser_telephonyPackage_returnsTrue()
            throws Exception {
        mockHeadlessSystemUserMode(true);

        // Current user is the main user.
        when(mUserManager.getMainUser()).thenReturn(NON_SYSTEM_USER);
        mUserId = NON_SYSTEM_USER_ID;

        mockContextForFullUser();
        mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);

        ApplicationInfo applicationInfo = getApplicationInfo(
                Process.SYSTEM_UID, ApplicationInfo.FLAG_ALLOW_BACKUP,
                CUSTOM_BACKUP_AGENT_NAME, UserBackupManagerService.TELEPHONY_PROVIDER_PACKAGE);

        boolean isEligible = mBackupEligibilityRules.appIsEligibleForBackup(applicationInfo);

        // Telephony package is allowed for the main user in HSUM.
        assertThat(isEligible).isTrue();
    }

    @Test
    public void appIsEligibleForBackup_systemUid_hsumNonMainUser_telephonyPackage_returnsFalse()
            throws Exception {
        mockHeadlessSystemUserMode(true);

        // Current user is a non-main user.
        when(mUserManager.getMainUser()).thenReturn(NON_SYSTEM_USER);
        mUserId = NON_SYSTEM_USER_ID + 1;

        mockContextForFullUser();
        mBackupEligibilityRules = getBackupEligibilityRules(BackupDestination.CLOUD);

        ApplicationInfo applicationInfo = getApplicationInfo(
                Process.SYSTEM_UID, ApplicationInfo.FLAG_ALLOW_BACKUP,
                CUSTOM_BACKUP_AGENT_NAME, UserBackupManagerService.TELEPHONY_PROVIDER_PACKAGE);

        boolean isEligible = mBackupEligibilityRules.appIsEligibleForBackup(applicationInfo);

        // Telephony package is not allowed for non-main users in HSUM.
        assertThat(isEligible).isFalse();
    }

    @Test
    public void appIsEligibleForBackup_systemUid_profileUser_notAllowedPackage_returnsFalse()
            throws Exception {
@@ -865,6 +919,10 @@ public class BackupEligibilityRulesTest {
                backupDestination, skipRestoreForLaunchedApps);
    }

    private static void mockHeadlessSystemUserMode(boolean isHeadless) {
        doReturn(isHeadless).when(UserManager::isHeadlessSystemUserMode);
    }

    private static Signature generateSignature(byte i) {
        byte[] signatureBytes = new byte[256];
        signatureBytes[0] = i;