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

Commit 90ad227c authored by Bernardo Rufino's avatar Bernardo Rufino Committed by Android (Google) Code Review
Browse files

Merge changes I7758e006,I49bb2315

* changes:
  Move getServiceForUserIfCallerHasPermission() to Trampoline
  Move dump() to Trampoline
parents 433bd939 aa032460
Loading
Loading
Loading
Loading
+0 −66
Original line number Diff line number Diff line
@@ -18,20 +18,13 @@ package com.android.server.backup;

import static com.android.internal.util.Preconditions.checkNotNull;

import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;

import java.io.FileDescriptor;
import java.io.PrintWriter;

/**
 * Definition of the system service that performs backup/restore operations.
 *
@@ -45,9 +38,6 @@ public class BackupManagerService {
    public static final boolean MORE_DEBUG = false;
    public static final boolean DEBUG_SCHEDULING = true;

    @VisibleForTesting
    static final String DUMP_RUNNING_USERS_MESSAGE = "Backup Manager is running for users:";

    private final Context mContext;
    private final Trampoline mTrampoline;
    private final SparseArray<UserBackupManagerService> mServiceUsers;
@@ -63,62 +53,6 @@ public class BackupManagerService {
        mServiceUsers = userServices;
    }

    /**
     * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
     * If the user is not registered with the service (either the user is locked or not eligible for
     * the backup service) then return {@code null}.
     *
     * @param userId The id of the user to retrieve its instance of {@link
     *     UserBackupManagerService}.
     * @param caller A {@link String} identifying the caller for logging purposes.
     * @throws SecurityException if {@code userId} is different from the calling user id and the
     *     caller does NOT have the android.permission.INTERACT_ACROSS_USERS_FULL permission.
     */
    @Nullable
    @VisibleForTesting
    UserBackupManagerService getServiceForUserIfCallerHasPermission(
            @UserIdInt int userId, String caller) {
        return mTrampoline.getServiceForUserIfCallerHasPermission(userId, caller);
    }

    /*
     * The following methods are implementations of IBackupManager methods called from Trampoline.
     * They delegate to the appropriate per-user instance of UserBackupManagerService to perform the
     * action on the passed in user. Currently this is a straight redirection (see TODO).
     */
    // TODO (b/118520567): Stop hardcoding system user when we pass in user id as a parameter

    // ---------------------------------------------
    //  SERVICE OPERATIONS
    // ---------------------------------------------

    /** Prints service state for 'dumpsys backup'. */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
            return;
        }

        if (args != null) {
            for (String arg : args) {
                if ("users".equals(arg.toLowerCase())) {
                    pw.print(DUMP_RUNNING_USERS_MESSAGE);
                    for (int i = 0; i < mServiceUsers.size(); i++) {
                        pw.print(" " + mServiceUsers.keyAt(i));
                    }
                    pw.println();
                    return;
                }
            }
        }

        UserBackupManagerService userBackupManagerService =
                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");

        if (userBackupManagerService != null) {
            userBackupManagerService.dump(fd, pw, args);
        }
    }

    /** Implementation to receive lifecycle event callbacks for system services. */
    public static class Lifecycle extends SystemService {
        public Lifecycle(Context context) {
+28 −4
Original line number Diff line number Diff line
@@ -90,6 +90,9 @@ import java.util.Set;
 * UserHandle#USER_SYSTEM} and disables backup for all users.
 */
public class Trampoline extends IBackupManager.Stub {
    @VisibleForTesting
    static final String DUMP_RUNNING_USERS_MESSAGE = "Backup Manager is running for users:";

    /**
     * Name of file that disables the backup service. If this file exists, then backup is disabled
     * for all users.
@@ -1442,12 +1445,33 @@ public class Trampoline extends IBackupManager.Stub {

    @Override
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
            return;
        }
        int userId = binderGetCallingUserId();
        if (isUserReadyForBackup(userId)) {
            mService.dump(fd, pw, args);
        } else {
        if (!isUserReadyForBackup(userId)) {
            pw.println("Inactive");
            return;
        }

        if (args != null) {
            for (String arg : args) {
                if ("users".equals(arg.toLowerCase())) {
                    pw.print(DUMP_RUNNING_USERS_MESSAGE);
                    for (int i = 0; i < mUserServices.size(); i++) {
                        pw.print(" " + mUserServices.keyAt(i));
                    }
                    pw.println();
                    return;
                }
            }
        }

        UserBackupManagerService userBackupManagerService =
                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");

        if (userBackupManagerService != null) {
            userBackupManagerService.dump(fd, pw, args);
        }
    }

+0 −123
Original line number Diff line number Diff line
@@ -17,18 +17,14 @@
package com.android.server.backup;

import static android.Manifest.permission.BACKUP;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;

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

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
@@ -63,10 +59,6 @@ import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContextWrapper;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

/** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */
@RunWith(RobolectricTestRunner.class)
@@ -165,53 +157,6 @@ public class BackupManagerServiceTest {
                                mContext, /* trampoline */ null, new SparseArray<>()));
    }

    /**
     * Test that the backup services throws a {@link SecurityException} if the caller does not have
     * INTERACT_ACROSS_USERS_FULL permission and passes a different user id.
     */
    @Test
    public void testGetServiceForUser_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
        registerUser(mUserOneId, mUserOneService);
        BackupManagerService backupManagerService = createService();
        setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);

        expectThrows(
                SecurityException.class,
                () ->
                        backupManagerService.getServiceForUserIfCallerHasPermission(
                                mUserOneId, "test"));
    }

    /**
     * Test that the backup services does not throw a {@link SecurityException} if the caller has
     * INTERACT_ACROSS_USERS_FULL permission and passes a different user id.
     */
    @Test
    public void testGetServiceForUserIfCallerHasPermission_withPermission_worksForNonCallingUser() {
        registerUser(mUserOneId, mUserOneService);
        BackupManagerService backupManagerService = createService();
        setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ true);

        assertEquals(
                mUserOneService,
                backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test"));
    }

    /**
     * Test that the backup services does not throw a {@link SecurityException} if the caller does
     * not have INTERACT_ACROSS_USERS_FULL permission and passes in the calling user id.
     */
    @Test
    public void testGetServiceForUserIfCallerHasPermission_withoutPermission_worksForCallingUser() {
        registerUser(mUserOneId, mUserOneService);
        BackupManagerService backupManagerService = createService();
        setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);

        assertEquals(
                mUserOneService,
                backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test"));
    }

    // ---------------------------------------------
    //  Lifecycle tests
    // ---------------------------------------------
@@ -253,74 +198,6 @@ public class BackupManagerServiceTest {
        verify(trampoline).onStopUser(UserHandle.USER_SYSTEM);
    }

    // ---------------------------------------------
    //  Service tests
    // ---------------------------------------------

    /** Test that the backup service routes methods correctly to the user that requests it. */
    @Test
    public void testDump_onRegisteredUser_callsMethodForUser() throws Exception {
        grantDumpPermissions();

        registerUser(UserHandle.USER_SYSTEM, mUserOneService);
        BackupManagerService backupManagerService = createService();
        File testFile = createTestFile();
        FileDescriptor fileDescriptor = new FileDescriptor();
        PrintWriter printWriter = new PrintWriter(testFile);
        String[] args = {"1", "2"};

        backupManagerService.dump(fileDescriptor, printWriter, args);

        verify(mUserOneService).dump(fileDescriptor, printWriter, args);
    }

    /** Test that the backup service does not route methods for non-registered users. */
    @Test
    public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception {
        grantDumpPermissions();

        BackupManagerService backupManagerService = createService();
        File testFile = createTestFile();
        FileDescriptor fileDescriptor = new FileDescriptor();
        PrintWriter printWriter = new PrintWriter(testFile);
        String[] args = {"1", "2"};

        backupManagerService.dump(fileDescriptor, printWriter, args);

        verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args);
    }

    /** Test that 'dumpsys backup users' dumps the list of users registered in backup service*/
    @Test
    public void testDump_users_dumpsListOfRegisteredUsers() {
        grantDumpPermissions();

        registerUser(mUserOneId, mUserOneService);
        BackupManagerService backupManagerService = createService();
        StringWriter out = new StringWriter();
        PrintWriter writer = new PrintWriter(out);
        String[] args = {"users"};

        backupManagerService.dump(null, writer, args);

        writer.flush();
        assertEquals(
                String.format("%s %d\n", BackupManagerService.DUMP_RUNNING_USERS_MESSAGE,
                        mUserOneId),
                out.toString());
    }

    private void grantDumpPermissions() {
        mShadowContext.grantPermissions(DUMP);
        mShadowContext.grantPermissions(PACKAGE_USAGE_STATS);
    }

    private File createTestFile() throws IOException {
        File testFile = new File(mContext.getFilesDir(), "test");
        testFile.createNewFile();
        return testFile;
    }

    private BackupManagerService createService() {
        mShadowContext.grantPermissions(BACKUP);
        return new BackupManagerService(mContext, mTrampoline, mTrampoline.getUserServices());
+121 −1
Original line number Diff line number Diff line
@@ -17,12 +17,15 @@
package com.android.server.backup;

import static android.Manifest.permission.BACKUP;
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.PACKAGE_USAGE_STATS;

import static com.android.server.backup.testing.TransportData.backupTransport;

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

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -64,6 +67,10 @@ import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowContextWrapper;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

/** Tests for {@link com.android.server.backup.Trampoline}. */
@RunWith(RobolectricTestRunner.class)
@@ -86,6 +93,7 @@ public class TrampolineRoboTest {
    private ShadowUserManager mShadowUserManager;
    @UserIdInt private int mUserOneId;
    @UserIdInt private int mUserTwoId;
    @Mock private UserBackupManagerService mUserSystemService;
    @Mock private UserBackupManagerService mUserOneService;
    @Mock private UserBackupManagerService mUserTwoService;

@@ -1414,13 +1422,125 @@ public class TrampolineRoboTest {
                        observer);
    }

    // ---------------------------------------------
    //  Service tests
    // ---------------------------------------------

    /** Test that the backup service routes methods correctly to the user that requests it. */
    @Test
    public void testDump_onRegisteredUser_callsMethodForUser() throws Exception {
        grantDumpPermissions();
        Trampoline backupManagerService = createSystemRegisteredService();
        File testFile = createTestFile();
        FileDescriptor fileDescriptor = new FileDescriptor();
        PrintWriter printWriter = new PrintWriter(testFile);
        String[] args = {"1", "2"};
        ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM));

        backupManagerService.dump(fileDescriptor, printWriter, args);

        verify(mUserSystemService).dump(fileDescriptor, printWriter, args);
    }

    /** Test that the backup service does not route methods for non-registered users. */
    @Test
    public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception {
        grantDumpPermissions();
        Trampoline backupManagerService = createService();
        File testFile = createTestFile();
        FileDescriptor fileDescriptor = new FileDescriptor();
        PrintWriter printWriter = new PrintWriter(testFile);
        String[] args = {"1", "2"};

        backupManagerService.dump(fileDescriptor, printWriter, args);

        verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args);
    }

    /** Test that 'dumpsys backup users' dumps the list of users registered in backup service*/
    @Test
    public void testDump_users_dumpsListOfRegisteredUsers() {
        grantDumpPermissions();
        Trampoline backupManagerService = createSystemRegisteredService();
        registerUser(backupManagerService, mUserOneId, mUserOneService);
        StringWriter out = new StringWriter();
        PrintWriter writer = new PrintWriter(out);
        String[] args = {"users"};

        backupManagerService.dump(null, writer, args);

        writer.flush();
        assertEquals(
                String.format("%s %d %d\n", Trampoline.DUMP_RUNNING_USERS_MESSAGE,
                        UserHandle.USER_SYSTEM, mUserOneId),
                out.toString());
    }

    private File createTestFile() throws IOException {
        File testFile = new File(mContext.getFilesDir(), "test");
        testFile.createNewFile();
        return testFile;
    }

    private void grantDumpPermissions() {
        mShadowContext.grantPermissions(DUMP);
        mShadowContext.grantPermissions(PACKAGE_USAGE_STATS);
    }

    /**
     * Test that the backup services throws a {@link SecurityException} if the caller does not have
     * INTERACT_ACROSS_USERS_FULL permission and passes a different user id.
     */
    @Test
    public void testGetServiceForUser_withoutPermission_throwsSecurityExceptionForNonCallingUser() {
        Trampoline backupManagerService = createService();
        registerUser(backupManagerService, mUserOneId, mUserOneService);
        setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ false);

        expectThrows(
                SecurityException.class,
                () ->
                        backupManagerService.getServiceForUserIfCallerHasPermission(
                                mUserOneId, "test"));
    }

    /**
     * Test that the backup services does not throw a {@link SecurityException} if the caller has
     * INTERACT_ACROSS_USERS_FULL permission and passes a different user id.
     */
    @Test
    public void testGetServiceForUserIfCallerHasPermission_withPermission_worksForNonCallingUser() {
        Trampoline backupManagerService = createService();
        registerUser(backupManagerService, mUserOneId, mUserOneService);
        setCallerAndGrantInteractUserPermission(mUserTwoId, /* shouldGrantPermission */ true);

        assertEquals(
                mUserOneService,
                backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test"));
    }

    /**
     * Test that the backup services does not throw a {@link SecurityException} if the caller does
     * not have INTERACT_ACROSS_USERS_FULL permission and passes in the calling user id.
     */
    @Test
    public void testGetServiceForUserIfCallerHasPermission_withoutPermission_worksForCallingUser() {
        Trampoline backupManagerService = createService();
        registerUser(backupManagerService, mUserOneId, mUserOneService);
        setCallerAndGrantInteractUserPermission(mUserOneId, /* shouldGrantPermission */ false);

        assertEquals(
                mUserOneService,
                backupManagerService.getServiceForUserIfCallerHasPermission(mUserOneId, "test"));
    }

    private Trampoline createService() {
        return new Trampoline(mContext);
    }

    private Trampoline createSystemRegisteredService() {
        Trampoline trampoline = createService();
        registerUser(trampoline, UserHandle.USER_SYSTEM, mock(UserBackupManagerService.class));
        registerUser(trampoline, UserHandle.USER_SYSTEM, mUserSystemService);
        return trampoline;
    }

+0 −12
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

@@ -541,17 +540,6 @@ public class TrampolineTest {
        verifyNoMoreInteractions(mBackupManagerServiceMock);
    }

    @Test
    public void dump_callerHasPermission_forwarded() {
        when(mContextMock.checkCallingOrSelfPermission(
                android.Manifest.permission.DUMP)).thenReturn(
                PackageManager.PERMISSION_GRANTED);

        mTrampoline.dump(mFileDescriptorStub, mPrintWriterMock, null);

        verify(mBackupManagerServiceMock).dump(mFileDescriptorStub, mPrintWriterMock, null);
    }

    public void testGetUserForAncestralSerialNumber() {
        TrampolineTestable.sBackupDisabled = false;
        Trampoline trampoline = new TrampolineTestable(mContextMock, mUserServices);