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

Commit 2d9cc70c authored by Tim Murray's avatar Tim Murray
Browse files

Revert "Add LifecycleOperationStorage and tests"

This reverts commit a67d744a.

Reason for revert: 4MB services.odex size regression

bug: 161089758
test: TH
Change-Id: Ifa6f89e2282a3cfd94056b3c409904b7bad77026
parent a67d744a
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -19,9 +19,5 @@ java_library_static {
    defaults: ["platform_service_defaults"],
    srcs: [":services.backup-sources"],
    libs: ["services.core"],
    static_libs: [
        "backuplib",
        "app-compat-annotations",
        "guava",
    ],
    static_libs: ["backuplib", "app-compat-annotations"],
}
+0 −141
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.annotation.IntDef;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;

/**
 * OperationStorage is an abstraction around a set of active operations.
 *
 * Operations are registered with a token that must first be obtained from
 * {@link UserBackupManagerService#generateRandomIntegerToken()}.  When
 * registering, the caller may also associate a set of package names with
 * the operation.
 *
 * TODO(b/208442527): have the token be generated within and returned by
 *                    registerOperation, as it should be an internal detail.
 *
 * Operations have a type and a state.  Although ints, the values that can
 * be used are defined in {@link UserBackupManagerService}.  If the type of
 * an operation is OP_BACKUP, then it represents a task running backups. The
 * task is provided when registering the operation because it provides a
 * handle to cancel the backup.
 */
public interface OperationStorage {

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
        OpState.PENDING,
        OpState.ACKNOWLEDGED,
        OpState.TIMEOUT
    })
    public @interface OpState {
        // The operation is in progress.
        int PENDING = 0;
        // The operation has been acknowledged.
        int ACKNOWLEDGED = 1;
        // The operation has timed out.
        int TIMEOUT = -1;
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
        OpType.BACKUP_WAIT,
        OpType.RESTORE_WAIT,
        OpType.BACKUP,
    })
    public @interface OpType {
        // Waiting for backup agent to respond during backup operation.
        int BACKUP_WAIT = 0;
        // Waiting for backup agent to respond during restore operation.
        int RESTORE_WAIT = 1;
        // An entire backup operation spanning multiple packages.
        int BACKUP = 2;
    }

    /**
     * Record an ongoing operation of given type and in the given initial
     * state. The associated task is used as a callback.
     *
     * @param token        an operation token issued by
     *                     {@link UserBackupManagerService#generateRandomIntegerToken()}
     * @param initialState the state that the operation starts in
     * @param task         the {@link BackupRestoreTask} that is expected to
     *                     remove the operation on completion, and which may
     *                     be notified if the operation requires cancelling.
     * @param type         the type of the operation.
     */
    void registerOperation(int token, @OpState int initialState,
            BackupRestoreTask task, @OpType int type);

    /**
     * See {@link #registerOperation()}.  In addition this method accepts a set
     * of package names which are associated with the operation.
     *
     * @param token        See {@link #registerOperation()}
     * @param initialState See {@link #registerOperation()}
     * @param packageNames the package names to associate with the operation.
     * @param task         See {@link #registerOperation()}
     * @param type         See {@link #registerOperation()}
     */
    void registerOperationForPackages(int token, @OpState int initialState,
            Set<String> packageNames, BackupRestoreTask task, @OpType int type);

    /**
     * Remove the operation identified by token.  This is called when the
     * operation is no longer in progress and should be dropped. Any association
     * with package names provided in {@link #registerOperation()} is dropped as
     * well.
     *
     * @param token the operation token specified when registering the operation.
     */
    void removeOperation(int token);

    /**
     * Obtain a set of operation tokens for all pending operations that were
     * registered with an association to the specified package name.
     *
     * @param packageName the name of the package used at registration time
     *
     * @return a set of operation tokens associated to package name.
     */
    Set<Integer> operationTokensForPackage(String packageName);

    /**
     * Obtain a set of operation tokens for all pending operations that are
     * of the specified operation type.
     *
     * @param type the type of the operation provided at registration time.
     *
     * @return a set of operation tokens for operations of that type.
     */
    Set<Integer> operationTokensForOpType(@OpType int type);

    /**
     * Obtain a set of operation tokens for all pending operations that are
     * currently in the specified operation state.
     *
     * @param state the state of the operation.
     *
     * @return a set of operation tokens for operations in that state.
     */
    Set<Integer> operationTokensForOpState(@OpState int state);
};
+2 −2
Original line number Diff line number Diff line
@@ -290,8 +290,8 @@ public class UserBackupManagerService {
    // Bookkeeping of in-flight operations. The operation token is the index of the entry in the
    // pending operations list.
    public static final int OP_PENDING = 0;
    public static final int OP_ACKNOWLEDGED = 1;
    public static final int OP_TIMEOUT = -1;
    private static final int OP_ACKNOWLEDGED = 1;
    private static final int OP_TIMEOUT = -1;

    // Waiting for backup agent to respond during backup operation.
    public static final int OP_TYPE_BACKUP_WAIT = 0;
+0 −337
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.internal;

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

import android.annotation.UserIdInt;
import android.util.Slog;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.OperationStorage;

import com.google.common.collect.ImmutableSet;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.IntConsumer;

import javax.annotation.concurrent.ThreadSafe;

/**
 * LifecycleOperationStorage is responsible for maintaining a set of currently
 * active operations.  Each operation has a type and state, and a callback that
 * can receive events upon operation completion or cancellation.  It may also
 * be associated with one or more package names.
 *
 * An operation wraps a {@link BackupRestoreTask} within it.
 * It's the responsibility of this task to remove the operation from this array.
 *
 * If type of operation is {@code OP_TYPE_WAIT}, it is waiting for an ACK or
 * timeout.
 *
 * A BackupRestore task gets notified of AVK/timeout for the operation via
 * {@link BackupRestoreTask#handleCancel()},
 * {@link BackupRestoreTask#operationComplete()} and {@code notifyAll} called
 * on the {@code mCurrentOpLock}.
 *
 * {@link LifecycleOperationStorage#waitUntilOperationComplete(int)} is used in
 * various places to 'wait' for notifyAll and detect change of pending state of
 * an operation. So typically, an operation will be removed from this array by:
 * - {@link BackupRestoreTask#handleCancel()} and
 * - {@link BackupRestoreTask#operationComplete()} OR
 *   {@link BackupRestoreTask#waitUntilOperationComplete()}.
 * Do not remove at both these places because {@code waitUntilOperationComplete}
 * relies on the operation being present to determine its completion status.
 *
 * If type of operation is {@code OP_BACKUP}, it is a task running backups. It
 * provides a handle to cancel backup tasks.
 */
@ThreadSafe
public class LifecycleOperationStorage implements OperationStorage {
    private static final String TAG = "LifecycleOperationStorage";

    private final int mUserId;

    private final Object mOperationsLock = new Object();

    // Bookkeeping of in-flight operations. The operation token is the index of
    // the entry in the pending operations list.
    @GuardedBy("mOperationsLock")
    private final SparseArray<Operation> mOperations = new SparseArray<>();

    // Association from package name to one or more operations relating to that
    // package.
    @GuardedBy("mOperationsLock")
    private final Map<String, Set<Integer>> mOpTokensByPackage = new HashMap<>();

    public LifecycleOperationStorage(@UserIdInt int userId) {
        this.mUserId = userId;
    }

    /** See {@link OperationStorage#registerOperation()} */
    @Override
    public void registerOperation(int token, @OpState int initialState,
            BackupRestoreTask task, @OpType int type) {
        registerOperationForPackages(token, initialState, ImmutableSet.of(), task, type);
    }

    /** See {@link OperationStorage#registerOperationForPackages()} */
    @Override
    public void registerOperationForPackages(int token, @OpState int initialState,
            Set<String> packageNames, BackupRestoreTask task, @OpType int type) {
        synchronized (mOperationsLock) {
            mOperations.put(token, new Operation(initialState, task, type));
            for (String packageName : packageNames) {
                Set<Integer> tokens = mOpTokensByPackage.get(packageName);
                if (tokens == null) {
                    tokens = new HashSet<Integer>();
                }
                tokens.add(token);
                mOpTokensByPackage.put(packageName, tokens);
            }
        }
    }

    /** See {@link OperationStorage#removeOperation()} */
    @Override
    public void removeOperation(int token) {
        synchronized (mOperationsLock) {
            mOperations.remove(token);
            ImmutableSet<String> packagesWithTokens =
                    ImmutableSet.copyOf(mOpTokensByPackage.keySet());
            for (String packageName : packagesWithTokens) {
                Set<Integer> tokens = mOpTokensByPackage.get(packageName);
                if (tokens == null) {
                    continue;
                }
                tokens.remove(token);
                mOpTokensByPackage.put(packageName, tokens);
            }
        }
    }

    /** See {@link OperationStorage#operationTokensForPackage()} */
    @Override
    public Set<Integer> operationTokensForPackage(String packageName) {
        synchronized (mOperationsLock) {
            Set<Integer> tokens = mOpTokensByPackage.get(packageName);
            if (tokens == null) {
                return ImmutableSet.of();
            }
            return ImmutableSet.copyOf(tokens);
        }
    }

    /** See {@link OperationStorage#operationTokensForOpType()} */
    @Override
    public Set<Integer> operationTokensForOpType(@OpType int type) {
        ImmutableSet.Builder<Integer> tokens = ImmutableSet.builder();
        synchronized (mOperationsLock) {
            for (int i = 0; i < mOperations.size(); i++) {
                final Operation op = mOperations.valueAt(i);
                final int token = mOperations.keyAt(i);
                if (op.type == type) {
                    tokens.add(token);
                }
            }
            return tokens.build();
        }
    }

    /** See {@link OperationStorage#operationTokensForOpState()} */
    @Override
    public Set<Integer> operationTokensForOpState(@OpState int state) {
        ImmutableSet.Builder<Integer> tokens = ImmutableSet.builder();
        synchronized (mOperationsLock) {
            for (int i = 0; i < mOperations.size(); i++) {
                final Operation op = mOperations.valueAt(i);
                final int token = mOperations.keyAt(i);
                if (op.state == state) {
                    tokens.add(token);
                }
            }
            return tokens.build();
        }
    }

    /**
     * A blocking function that blocks the caller until the operation identified
     * by {@code token} is complete - either via a message from the backup,
     * agent or through cancellation.
     *
     * @param token the operation token specified when registering the operation
     * @param callback a lambda which is invoked once only when the operation
     *                 completes - ie. if this method is called twice for the
     *                 same token, the lambda is not invoked the second time.
     * @return true if the operation was ACKed prior to or during this call.
     */
    public boolean waitUntilOperationComplete(int token, IntConsumer callback) {
        if (MORE_DEBUG) {
            Slog.i(TAG, "[UserID:" + mUserId + "] Blocking until operation complete for "
                    + Integer.toHexString(token));
        }
        @OpState int finalState = OpState.PENDING;
        Operation op = null;
        synchronized (mOperationsLock) {
            while (true) {
                op = mOperations.get(token);
                if (op == null) {
                    // mysterious disappearance: treat as success with no callback
                    break;
                } else {
                    if (op.state == OpState.PENDING) {
                        try {
                            mOperationsLock.wait();
                        } catch (InterruptedException e) {
                            Slog.w(TAG, "Waiting on mOperationsLock: ", e);
                        }
                        // When the wait is notified we loop around and recheck the current state
                    } else {
                        if (MORE_DEBUG) {
                            Slog.d(TAG, "[UserID:" + mUserId
                                    + "] Unblocked waiting for operation token="
                                    + Integer.toHexString(token));
                        }
                        // No longer pending; we're done
                        finalState = op.state;
                        break;
                    }
                }
            }
        }

        removeOperation(token);
        if (op != null) {
            callback.accept(op.type);
        }
        if (MORE_DEBUG) {
            Slog.v(TAG, "[UserID:" + mUserId + "] operation " + Integer.toHexString(token)
                    + " complete: finalState=" + finalState);
        }
        return finalState == OpState.ACKNOWLEDGED;
    }

    /**
     * Signals that an ongoing operation is complete: after a currently-active
     * backup agent has notified us that it has completed the outstanding
     * asynchronous backup/restore operation identified by the supplied
     * {@code} token.
     *
     * @param token the operation token specified when registering the operation
     * @param result a result code or error code for the completed operation
     * @param callback a lambda that is invoked if the completion moves the
     *                 operation from PENDING to ACKNOWLEDGED state.
     */
    public void onOperationComplete(int token, long result, Consumer<BackupRestoreTask> callback) {
        if (MORE_DEBUG) {
            Slog.v(TAG, "[UserID:" + mUserId + "] onOperationComplete: "
                    + Integer.toHexString(token) + " result=" + result);
        }
        Operation op = null;
        synchronized (mOperationsLock) {
            op = mOperations.get(token);
            if (op != null) {
                if (op.state == OpState.TIMEOUT) {
                    // The operation already timed out, and this is a late response.  Tidy up
                    // and ignore it; we've already dealt with the timeout.
                    op = null;
                    mOperations.remove(token);
                } else if (op.state == OpState.ACKNOWLEDGED) {
                    if (DEBUG) {
                        Slog.w(TAG, "[UserID:" + mUserId + "] Received duplicate ack for token="
                                + Integer.toHexString(token));
                    }
                    op = null;
                    mOperations.remove(token);
                } else if (op.state == OpState.PENDING) {
                    // Can't delete op from mOperations. waitUntilOperationComplete can be
                    // called after we we receive this call.
                    op.state = OpState.ACKNOWLEDGED;
                }
            }
            mOperationsLock.notifyAll();
        }

        // Invoke the operation's completion callback, if there is one.
        if (op != null && op.callback != null) {
            callback.accept(op.callback);
        }
    }

    /**
     * Cancel the operation associated with {@code token}.  Cancellation may be
     * propagated to the operation's callback (a {@link BackupRestoreTask}) if
     * the operation has one, and the cancellation is due to the operation
     * timing out.
     *
     * @param token the operation token specified when registering the operation
     * @param cancelAll this is passed on when propagating the cancellation
     * @param operationTimedOutCallback a lambda that is invoked with the
     *                                  operation type where the operation is
     *                                  cancelled due to timeout, allowing the
     *                                  caller to do type-specific clean-ups.
     */
    public void cancelOperation(
            int token, boolean cancelAll, IntConsumer operationTimedOutCallback) {
        // Notify any synchronous waiters
        Operation op = null;
        synchronized (mOperationsLock) {
            op = mOperations.get(token);
            if (MORE_DEBUG) {
                if (op == null) {
                    Slog.w(TAG, "[UserID:" + mUserId + "] Cancel of token "
                            + Integer.toHexString(token) + " but no op found");
                }
            }
            int state = (op != null) ? op.state : OpState.TIMEOUT;
            if (state == OpState.ACKNOWLEDGED) {
                // The operation finished cleanly, so we have nothing more to do.
                if (DEBUG) {
                    Slog.w(TAG, "[UserID:" + mUserId + "] Operation already got an ack."
                            + "Should have been removed from mCurrentOperations.");
                }
                op = null;
                mOperations.delete(token);
            } else if (state == OpState.PENDING) {
                if (DEBUG) {
                    Slog.v(TAG, "[UserID:" + mUserId + "] Cancel: token="
                            + Integer.toHexString(token));
                }
                op.state = OpState.TIMEOUT;
                // Can't delete op from mOperations here. waitUntilOperationComplete may be
                // called after we receive cancel here. We need this op's state there.
                operationTimedOutCallback.accept(op.type);
            }
            mOperationsLock.notifyAll();
        }

        // If there's a TimeoutHandler for this event, call it
        if (op != null && op.callback != null) {
            if (MORE_DEBUG) {
                Slog.v(TAG, "[UserID:" + mUserId + "   Invoking cancel on " + op.callback);
            }
            op.callback.handleCancel(cancelAll);
        }
    }
};
+0 −174

File deleted.

Preview size limit exceeded, changes collapsed.