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

Commit 1ba241be authored by Sarp Misoglu's avatar Sarp Misoglu Committed by Android (Google) Code Review
Browse files

Merge "Move BackupAgent connection to a helper class" into main

parents 280d19b9 f658e370
Loading
Loading
Loading
Loading
+318 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.backup;

import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;

import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ApplicationThreadConstants;
import android.app.IActivityManager;
import android.app.IBackupAgent;
import android.app.backup.BackupAnnotations;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.backup.internal.LifecycleOperationStorage;

import java.util.Set;

/**
 * Handles the lifecycle of {@link IBackupAgent}s that the {@link UserBackupManagerService}
 * communicates with.
 *
 * <p>There can only be one agent that's connected to at a time.
 *
 * <p>There should be only one instance of this class per {@link UserBackupManagerService}.
 */
public class BackupAgentConnectionManager {

    /**
     * Enables the OS making a decision on whether backup restricted mode should be used for apps
     * that haven't explicitly opted in or out. See
     * {@link android.content.pm.PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details.
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
    public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510;

    // The thread performing the sequence of queued backups binds to each app's agent
    // in succession.  Bind notifications are asynchronously delivered through the
    // Activity Manager; use this lock object to signal when a requested binding has
    // completed.
    private final Object mAgentConnectLock = new Object();
    private IBackupAgent mConnectedAgent;
    private volatile boolean mConnecting;
    private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
    private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();

    private final IActivityManager mActivityManager;
    private final ActivityManagerInternal mActivityManagerInternal;
    private final LifecycleOperationStorage mOperationStorage;
    private final PackageManager mPackageManager;
    private final UserBackupManagerService mUserBackupManagerService;
    private final int mUserId;
    private final String mUserIdMsg;

    BackupAgentConnectionManager(LifecycleOperationStorage operationStorage,
            PackageManager packageManager, UserBackupManagerService userBackupManagerService,
            int userId) {
        mActivityManager = ActivityManager.getService();
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
        mOperationStorage = operationStorage;
        mPackageManager = packageManager;
        mUserBackupManagerService = userBackupManagerService;
        mUserId = userId;
        mUserIdMsg = "[UserID:" + userId + "] ";
    }

    /**
     * Fires off a backup agent, blocking until it attaches (and ActivityManager will call
     * {@link #agentConnected(String, IBinder)}) or until this operation times out.
     *
     * @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
     */
    @Nullable
    public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
            @BackupAnnotations.BackupDestination int backupDestination) {
        IBackupAgent agent = null;
        synchronized (mAgentConnectLock) {
            mConnecting = true;
            mConnectedAgent = null;
            boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
                    app.packageName);
            try {
                if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
                        backupDestination, useRestrictedMode)) {
                    Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app);

                    // success; wait for the agent to arrive
                    // only wait 10 seconds for the bind to happen
                    long timeoutMark = System.currentTimeMillis() + 10 * 1000;
                    while (mConnecting && mConnectedAgent == null && (System.currentTimeMillis()
                            < timeoutMark)) {
                        try {
                            mAgentConnectLock.wait(5000);
                        } catch (InterruptedException e) {
                            // just bail
                            Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
                            mConnecting = false;
                            mConnectedAgent = null;
                        }
                    }

                    // if we timed out with no connect, abort and move on
                    if (mConnecting) {
                        Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
                        mConnectedAgent = null;
                    }
                    Slog.i(TAG, mUserIdMsg + "got agent " + mConnectedAgent);
                    agent = mConnectedAgent;
                }
            } catch (RemoteException e) {
                // can't happen - ActivityManager is local
            }
        }
        if (agent == null) {
            mActivityManagerInternal.clearPendingBackup(mUserId);
        }
        return agent;
    }

    /**
     * Tell the ActivityManager that we are done with the {@link IBackupAgent} of this {@code app}.
     * It will tell the app to destroy the agent.
     */
    public void unbindAgent(ApplicationInfo app) {
        try {
            mActivityManager.unbindBackupAgent(app);
        } catch (RemoteException e) {
            // Can't happen - activity manager is local
        }
    }

    /**
     * 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
     * {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}.
     */
    public void agentConnected(String packageName, IBinder agentBinder) {
        synchronized (mAgentConnectLock) {
            if (getCallingUid() == android.os.Process.SYSTEM_UID) {
                Slog.d(TAG,
                        mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
                mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
                mConnecting = false;
            } else {
                Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
                        + " claiming agent connected");
            }
            mAgentConnectLock.notifyAll();
        }
    }

    /**
     * Callback: a backup agent has failed to come up, or has unexpectedly quit. If the agent failed
     * to come up in the first place, the agentBinder argument will be {@code null}. This should
     * only be called from the {@link ActivityManager}.
     */
    public void agentDisconnected(String packageName) {
        synchronized (mAgentConnectLock) {
            if (getCallingUid() == Process.SYSTEM_UID) {
                mConnectedAgent = null;
                mConnecting = false;
            } else {
                Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
                        + " claiming agent disconnected");
            }
            Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName
                    + " died: cancel current operations");

            // Offload operation cancellation off the main thread as the cancellation callbacks
            // might call out to BackupTransport. Other operations started on the same package
            // before the cancellation callback has executed will also be cancelled by the callback.
            Runnable cancellationRunnable = () -> {
                // handleCancel() causes the PerformFullTransportBackupTask to go on to
                // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
                // that the package being backed up doesn't get stuck in restricted mode until the
                // backup time-out elapses.
                for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
                    if (MORE_DEBUG) {
                        Slog.d(TAG,
                                mUserIdMsg + "agentDisconnected: will handleCancel(all) for token:"
                                        + Integer.toHexString(token));
                    }
                    mUserBackupManagerService.handleCancel(token, true /* cancelAll */);
                }
            };
            getThreadForCancellation(cancellationRunnable).start();

            mAgentConnectLock.notifyAll();
        }
    }

    /**
     * Marks the given set of packages as packages that should not be put into restricted mode if
     * they are started for the given {@link BackupAnnotations.OperationType}.
     */
    public void setNoRestrictedModePackages(Set<String> packageNames,
            @BackupAnnotations.OperationType int opType) {
        if (opType == BackupAnnotations.OperationType.BACKUP) {
            mBackupNoRestrictedModePackages.clear();
            mBackupNoRestrictedModePackages.addAll(packageNames);
        } else if (opType == BackupAnnotations.OperationType.RESTORE) {
            mRestoreNoRestrictedModePackages.clear();
            mRestoreNoRestrictedModePackages.addAll(packageNames);
        } else {
            throw new IllegalArgumentException("opType must be BACKUP or RESTORE");
        }
    }

    /**
     * Clears the list of packages that should not be put into restricted mode for either backup or
     * restore.
     */
    public void clearNoRestrictedModePackages() {
        mBackupNoRestrictedModePackages.clear();
        mRestoreNoRestrictedModePackages.clear();
    }

    /**
     * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then
     * its value is returned. If it hasn't and it targets an SDK below
     * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then
     * returns the decision made by the {@link android.app.backup.BackupTransport}.
     *
     * <p>When this method is called, we should have already asked the transport and cached its
     * response in {@link #mBackupNoRestrictedModePackages} or
     * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without
     * any IPC to the transport.
     */
    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;
        }

        try {
            PackageManager.Property property = mPackageManager.getPropertyAsUser(
                    PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE,
                    packageName, /* className= */ null, mUserId);
            if (property.isBoolean()) {
                // If the package has explicitly specified, we won't ask the transport.
                return property.getBoolean();
            } else {
                Slog.w(TAG,
                        PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE + "must be a boolean.");
            }
        } catch (NameNotFoundException e) {
            // This is expected when the package has not defined the property in its manifest.
        }

        // The package has not specified the property. The behavior depends on the package's
        // targetSdk.
        // <36 gets the old behavior of always using restricted mode.
        if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName,
                UserHandle.of(mUserId))) {
            return true;
        }

        // Apps targeting >=36 get the behavior decided by the transport.
        // By this point, we should have asked the transport and cached its decision.
        if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL
                && mBackupNoRestrictedModePackages.contains(packageName)) || (
                mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL
                        && mRestoreNoRestrictedModePackages.contains(packageName))) {
            Slog.d(TAG, "Transport requested no restricted mode for: " + packageName);
            return false;
        }
        return true;
    }

    @VisibleForTesting
    Thread getThreadForCancellation(Runnable operation) {
        return new Thread(operation, /* operationName */ "agent-disconnected");
    }

    @VisibleForTesting
    int getCallingUid() {
        return Binder.getCallingUid();
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -658,7 +658,8 @@ public class BackupManagerService extends IBackupManager.Stub {
                getServiceForUserIfCallerHasPermission(userId, "agentConnected()");

        if (userBackupManagerService != null) {
            userBackupManagerService.agentConnected(packageName, agentBinder);
            userBackupManagerService.getBackupAgentConnectionManager().agentConnected(packageName,
                    agentBinder);
        }
    }

@@ -683,7 +684,8 @@ public class BackupManagerService extends IBackupManager.Stub {
                getServiceForUserIfCallerHasPermission(userId, "agentDisconnected()");

        if (userBackupManagerService != null) {
            userBackupManagerService.agentDisconnected(packageName);
            userBackupManagerService.getBackupAgentConnectionManager().agentDisconnected(
                    packageName);
        }
    }

+2 −1
Original line number Diff line number Diff line
@@ -146,7 +146,8 @@ public class KeyValueAdbBackupEngine {

    private IBackupAgent bindToAgent(ApplicationInfo targetApp) {
        try {
            return mBackupManagerService.bindToAgentSynchronous(targetApp,
            return mBackupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
                    targetApp,
                    ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL,
                    BackupAnnotations.BackupDestination.CLOUD);
        } catch (SecurityException e) {
+9 −257

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -314,7 +314,7 @@ public class FullBackupEngine {
                Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName);
            }
            mAgent =
                    backupManagerService.bindToAgentSynchronous(
                    backupManagerService.getBackupAgentConnectionManager().bindToAgentSynchronous(
                            mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL,
                            mBackupEligibilityRules.getBackupDestination());
        }
Loading