diff --git a/services/backup/Android.bp b/services/backup/Android.bp index 7b0d6c0a997c1..ead8aff30147f 100644 --- a/services/backup/Android.bp +++ b/services/backup/Android.bp @@ -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"], } diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java deleted file mode 100644 index 155a4b57072ea..0000000000000 --- a/services/backup/java/com/android/server/backup/OperationStorage.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 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 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 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 operationTokensForOpState(@OpState int state); -}; diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 2a6c90f706365..452adb2945404 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -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; diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java deleted file mode 100644 index b58c973d2aef2..0000000000000 --- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * 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 mOperations = new SparseArray<>(); - - // Association from package name to one or more operations relating to that - // package. - @GuardedBy("mOperationsLock") - private final Map> 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 packageNames, BackupRestoreTask task, @OpType int type) { - synchronized (mOperationsLock) { - mOperations.put(token, new Operation(initialState, task, type)); - for (String packageName : packageNames) { - Set tokens = mOpTokensByPackage.get(packageName); - if (tokens == null) { - tokens = new HashSet(); - } - tokens.add(token); - mOpTokensByPackage.put(packageName, tokens); - } - } - } - - /** See {@link OperationStorage#removeOperation()} */ - @Override - public void removeOperation(int token) { - synchronized (mOperationsLock) { - mOperations.remove(token); - ImmutableSet packagesWithTokens = - ImmutableSet.copyOf(mOpTokensByPackage.keySet()); - for (String packageName : packagesWithTokens) { - Set tokens = mOpTokensByPackage.get(packageName); - if (tokens == null) { - continue; - } - tokens.remove(token); - mOpTokensByPackage.put(packageName, tokens); - } - } - } - - /** See {@link OperationStorage#operationTokensForPackage()} */ - @Override - public Set operationTokensForPackage(String packageName) { - synchronized (mOperationsLock) { - Set tokens = mOpTokensByPackage.get(packageName); - if (tokens == null) { - return ImmutableSet.of(); - } - return ImmutableSet.copyOf(tokens); - } - } - - /** See {@link OperationStorage#operationTokensForOpType()} */ - @Override - public Set operationTokensForOpType(@OpType int type) { - ImmutableSet.Builder 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 operationTokensForOpState(@OpState int state) { - ImmutableSet.Builder 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 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); - } - } -}; diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java b/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java deleted file mode 100644 index c079c2da03266..0000000000000 --- a/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.verify; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.backup.BackupRestoreTask; -import com.android.server.backup.OperationStorage.OpState; -import com.android.server.backup.OperationStorage.OpType; - -import com.google.common.collect.ImmutableSet; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Set; - -@Presubmit -@RunWith(AndroidJUnit4.class) -public class LifecycleOperationStorageTest { - private static final int USER_ID = 0; - private static final int TOKEN_1 = 1; - private static final int TOKEN_2 = 2; - private static final long RESULT = 123L; - - private static final String PKG_FOO = "com.android.foo"; - private static final String PKG_BAR = "com.android.bar"; - private static final String PKG_BAZ = "com.android.baz"; - private static final ImmutableSet MULTIPLE_PKG = ImmutableSet.of(PKG_FOO); - private static final ImmutableSet MULTIPLE_PKGS_1 = ImmutableSet.of(PKG_FOO, PKG_BAR); - private static final ImmutableSet MULTIPLE_PKGS_2 = ImmutableSet.of(PKG_BAR, PKG_BAZ); - - @Mock private BackupRestoreTask mCallback; - private LifecycleOperationStorage mOpStorage; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(/* testClass */ this); - mOpStorage = new LifecycleOperationStorage(USER_ID); - } - - @After - public void tearDown() {} - - @Test - public void testRegisterOperation_singleOperation() throws Exception { - mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT); - - Set tokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT); - - assertThat(tokens).isEqualTo(only(TOKEN_1)); - } - - @Test - public void testRegisterOperation_multipleOperations() throws Exception { - mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT); - mOpStorage.registerOperation(TOKEN_2, OpState.ACKNOWLEDGED, mCallback, OpType.BACKUP_WAIT); - - Set typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT); - Set statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING); - Set stateAcknowledgedTokens = - mOpStorage.operationTokensForOpState(OpState.ACKNOWLEDGED); - - assertThat(typeWaitTokens).isEqualTo(ImmutableSet.of(TOKEN_1, TOKEN_2)); - assertThat(statePendingTokens).isEqualTo(only(TOKEN_1)); - assertThat(stateAcknowledgedTokens).isEqualTo(only(TOKEN_2)); - } - - @Test - public void testRegisterOperationForPackages_singlePackage() throws Exception { - mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING, - MULTIPLE_PKG, mCallback, OpType.BACKUP_WAIT); - - Set tokens = mOpStorage.operationTokensForPackage(PKG_FOO); - - assertThat(tokens).isEqualTo(only(TOKEN_1)); - } - - @Test - public void testRegisterOperationForPackages_multiplePackage() throws Exception { - mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING, - MULTIPLE_PKGS_1, mCallback, OpType.BACKUP); - mOpStorage.registerOperationForPackages(TOKEN_2, OpState.PENDING, - MULTIPLE_PKGS_2, mCallback, OpType.BACKUP); - - Set tokensFoo = mOpStorage.operationTokensForPackage(PKG_FOO); - Set tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR); - Set tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ); - - assertThat(tokensFoo).isEqualTo(only(TOKEN_1)); - assertThat(tokensBar).isEqualTo(ImmutableSet.of(TOKEN_1, TOKEN_2)); - assertThat(tokensBaz).isEqualTo(only(TOKEN_2)); - } - - @Test - public void testRemoveOperation() throws Exception { - mOpStorage.registerOperation(TOKEN_2, OpState.PENDING, mCallback, OpType.BACKUP_WAIT); - - Set typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT); - Set statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING); - - assertThat(typeWaitTokens).isEqualTo(only(TOKEN_2)); - assertThat(statePendingTokens).isEqualTo(only(TOKEN_2)); - - mOpStorage.removeOperation(TOKEN_2); - - typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT); - statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING); - - assertThat(typeWaitTokens).isEmpty(); - assertThat(statePendingTokens).isEmpty(); - } - - @Test - public void testRemoveOperation_removesPackageMappings() throws Exception { - mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING, MULTIPLE_PKGS_1, - mCallback, OpType.BACKUP); - mOpStorage.registerOperationForPackages(TOKEN_2, OpState.PENDING, MULTIPLE_PKGS_2, - mCallback, OpType.BACKUP); - - mOpStorage.removeOperation(TOKEN_2); - - Set tokensFoo = mOpStorage.operationTokensForPackage(PKG_FOO); - Set tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR); - Set tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ); - - assertThat(tokensFoo).isEqualTo(only(TOKEN_1)); - assertThat(tokensBar).isEqualTo(only(TOKEN_1)); - assertThat(tokensBaz).isEmpty(); - } - - @Test - public void testOnOperationComplete_pendingAdvancesState_invokesCallback() throws Exception { - mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT); - - mOpStorage.onOperationComplete(TOKEN_1, RESULT, callback -> { - mCallback.operationComplete(RESULT); - }); - - assertThat(mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT)) - .isEqualTo(only(TOKEN_1)); - assertThat(mOpStorage.operationTokensForOpState(OpState.PENDING)).isEmpty(); - assertThat(mOpStorage.operationTokensForOpState(OpState.ACKNOWLEDGED)).isNotEmpty(); - verify(mCallback).operationComplete(RESULT); - } - - private Set only(Integer val) { - return ImmutableSet.of(val); - } -}