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

Commit b87b9792 authored by Sarp Misoglu's avatar Sarp Misoglu
Browse files

Don't kill apps if they are not in restricted mode

Also respect the killAfterRestore flag for them.

Before this change, the android:killAfterRestore attribute was only
respected for apps that do Key/Value backups. This was because Full
backup apps used to always be started in restricted mode for B&R
operations and the framework needed to kill the app to reset the
restricted mode state.

Now, full backup apps may not be started in restricted mode
(after ag/30155472) so there's no need to kill them unless if they set
killAfterRestore.

For this to work, we need to keep track of whether an app is in
restricted mode. I've moved all connection/disconnection logic to the
new connection manager class so this is now fairly easy.

Note that apps will only not be in restricted mode if they are K/V or if
enable_restricted_mode_changes is enabled and they opted out. So I think
all the new behavior is guarded by the same flag.

Flag: com.android.server.backup.enable_restricted_mode_changes
Bug: 376661510
Test: atest BackupAgentConnectionManagerTest & atest CtsBackupHostTestCases
Change-Id: I1e94fb3b794961904a6c603ac34b00f3878f4e3e
parent 00ea92a5
Loading
Loading
Loading
Loading
+74 −21
Original line number Diff line number Diff line
@@ -101,11 +101,16 @@ public class BackupAgentConnectionManager {

    private static final class BackupAgentConnection {
        public final ApplicationInfo appInfo;
        public final int backupMode;
        public final boolean inRestrictedMode;
        public IBackupAgent backupAgent;
        public boolean connecting = true; // Assume we are trying to connect on creation.

        private BackupAgentConnection(ApplicationInfo appInfo) {
        private BackupAgentConnection(ApplicationInfo appInfo, int backupMode,
                boolean inRestrictedMode) {
            this.appInfo = appInfo;
            this.backupMode = backupMode;
            this.inRestrictedMode = inRestrictedMode;
        }
    }

@@ -113,26 +118,31 @@ public class BackupAgentConnectionManager {
     * Fires off a backup agent, blocking until it attaches (i.e. ActivityManager calls
     * {@link #agentConnected(String, IBinder)}) or until this operation times out.
     *
     * @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
     * @param backupMode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
     */
    @Nullable
    public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
    public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int backupMode,
            @BackupAnnotations.BackupDestination int backupDestination) {
        if (app == null) {
            Slog.w(TAG, mUserIdMsg + "bindToAgentSynchronous for null app");
            return null;
        }

        synchronized (mAgentConnectLock) {
            boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
            boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(backupMode,
                    app.packageName);
            if (mCurrentConnection != null) {
                Slog.e(TAG, mUserIdMsg + "binding to new agent before unbinding from old one: "
                        + mCurrentConnection.appInfo.packageName);
            }
            mCurrentConnection = new BackupAgentConnection(app);
            mCurrentConnection = new BackupAgentConnection(app, backupMode, useRestrictedMode);

            // bindBackupAgent() is an async API. It will kick off the app's process and call
            // agentConnected() when it receives the agent from the app.
            boolean startedBindSuccessfully = false;
            try {
                startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName, mode,
                        mUserId, backupDestination, useRestrictedMode);
                startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName,
                        backupMode, mUserId, backupDestination, useRestrictedMode);
            } catch (RemoteException e) {
                // can't happen - ActivityManager is local
            }
@@ -173,28 +183,66 @@ public class BackupAgentConnectionManager {
    /**
     * Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}.
     * It will tell the app to destroy the agent.
     *
     * <p>If {@code allowKill} is set, this will kill the app's process if the app is in restricted
     * mode or if it was started for restore and specified {@code android:killAfterRestore} in its
     * manifest.
     *
     * @see #shouldUseRestrictedBackupModeForPackage(int, String)
     */
    public void unbindAgent(ApplicationInfo app) {
    public void unbindAgent(ApplicationInfo app, boolean allowKill) {
        if (app == null) {
            Slog.w(TAG, mUserIdMsg + "unbindAgent for null app");
            return;
        }

        synchronized (mAgentConnectLock) {
            // Even if we weren't expecting to be bound to this agent, we should still call
            // ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
            try {
                mActivityManager.unbindBackupAgent(app);

                // Evaluate this before potentially setting mCurrentConnection = null.
                boolean willKill = allowKill && shouldKillAppOnUnbind(app);

                if (mCurrentConnection == null) {
                    Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
                } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
                Slog.w(TAG, mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
                    Slog.w(TAG,
                            mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
                                    + " expected: " + mCurrentConnection.appInfo.packageName);
                } else {
                    mCurrentConnection = null;
                }

            // Even if we weren't expecting to be bound to this agent, we should still call
            // ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
            try {
                mActivityManager.unbindBackupAgent(app);
                if (willKill) {
                    Slog.i(TAG, mUserIdMsg + "Killing agent host process");
                    mActivityManager.killApplicationProcess(app.processName, app.uid);
                }
            } catch (RemoteException e) {
                // Can't happen - activity manager is local
            }
        }
    }

    @GuardedBy("mAgentConnectLock")
    private boolean shouldKillAppOnUnbind(ApplicationInfo app) {
        // We don't ask system UID processes to be killed.
        if (UserHandle.isCore(app.uid)) {
            return false;
        }

        // If the app is in restricted mode or if we're not sure if it is because our internal
        // state is messed up, we need to avoid it being stuck in it.
        if (mCurrentConnection == null || mCurrentConnection.inRestrictedMode) {
            return true;
        }

        // App was doing restore and asked to be killed afterwards.
        return isBackupModeRestore(mCurrentConnection.backupMode)
                && (app.flags & ApplicationInfo.FLAG_KILL_AFTER_RESTORE) != 0;
    }

    /**
     * Callback: a requested backup agent has been instantiated. This should only be called from
     * the {@link ActivityManager} when it's telling us that an agent is ready after a call to
@@ -307,16 +355,16 @@ public class BackupAgentConnectionManager {
     */
    private boolean shouldUseRestrictedBackupModeForPackage(
            @BackupAnnotations.OperationType int mode, String packageName) {
        if (!Flags.enableRestrictedModeChanges()) {
            return true;
        }

        // Key/Value apps are never put in restricted mode.
        if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL
                || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) {
            return false;
        }

        if (!Flags.enableRestrictedModeChanges()) {
            return true;
        }

        try {
            PackageManager.Property property = mPackageManager.getPropertyAsUser(
                    PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
@@ -352,6 +400,11 @@ public class BackupAgentConnectionManager {
        return true;
    }

    private static boolean isBackupModeRestore(int backupMode) {
        return backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE
                || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL;
    }

    @VisibleForTesting
    Thread getThreadForCancellation(Runnable operation) {
        return new Thread(operation, /* operationName */ "agent-disconnected");
+2 −1
Original line number Diff line number Diff line
@@ -300,7 +300,8 @@ public class KeyValueAdbBackupEngine {
    }

    private void cleanup() {
        mBackupManagerService.tearDownAgentAndKill(mCurrentPackage.applicationInfo);
        mBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
                mCurrentPackage.applicationInfo, /* allowKill= */ true);
        mBlankStateName.delete();
        mNewStateName.delete();
        mBackupDataName.delete();
+0 −33
Original line number Diff line number Diff line
@@ -1998,39 +1998,6 @@ public class UserBackupManagerService {
        return mOperationStorage.isBackupOperationInProgress();
    }

    /** Unbind the backup agent and kill the app if it's a non-system app. */
    public void tearDownAgentAndKill(ApplicationInfo app) {
        if (app == null) {
            // Null means the system package, so just quietly move on.  :)
            return;
        }

        try {
            // unbind and tidy up even on timeout or failure, just in case
            mActivityManager.unbindBackupAgent(app);

            // The agent was running with a stub Application object, so shut it down.
            // !!! We hardcode the confirmation UI's package name here rather than use a
            //     manifest flag!  TODO something less direct.
            if (!UserHandle.isCore(app.uid)
                    && !app.packageName.equals("com.android.backupconfirm")) {
                if (MORE_DEBUG) {
                    Slog.d(TAG, addUserIdToLogMessage(mUserId, "Killing agent host process"));
                }
                mActivityManager.killApplicationProcess(app.processName, app.uid);
            } else {
                if (MORE_DEBUG) {
                    Slog.d(
                            TAG,
                            addUserIdToLogMessage(
                                    mUserId, "Not killing after operation: " + app.processName));
                }
            }
        } catch (RemoteException e) {
            Slog.d(TAG, addUserIdToLogMessage(mUserId, "Lost app trying to shut down"));
        }
    }

    // ----- Full-data backup scheduling -----

    /**
+2 −1
Original line number Diff line number Diff line
@@ -323,7 +323,8 @@ public class FullBackupEngine {

    private void tearDown() {
        if (mPkg != null) {
            backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo);
            backupManagerService.getBackupAgentConnectionManager().unbindAgent(
                    mPkg.applicationInfo, /* allowKill= */ true);
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -503,7 +503,8 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
            Slog.w(TAG, "adb backup cancel of " + target);
        }
        if (target != null) {
            mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
            mUserBackupManagerService.getBackupAgentConnectionManager().unbindAgent(
                    target.applicationInfo, /* allowKill= */ true);
        }
        mOperationStorage.removeOperation(mCurrentOpToken);
    }
Loading