diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 3df6e47a02440..136fada43b1fb 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -302,7 +302,7 @@ public class BackupHandler extends Handler { sets = transport.getAvailableRestoreSets(); // cache the result in the active session synchronized (params.session) { - params.session.mRestoreSets = sets; + params.session.setRestoreSets(sets); } if (sets == null) { EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java index e500d6e1b5ca2..5125b0d5234e1 100644 --- a/services/backup/java/com/android/server/backup/params/RestoreParams.java +++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java @@ -16,6 +16,7 @@ package com.android.server.backup.params; +import android.annotation.Nullable; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IRestoreObserver; import android.content.pm.PackageInfo; @@ -28,10 +29,10 @@ public class RestoreParams { public final IRestoreObserver observer; public final IBackupManagerMonitor monitor; public final long token; - public final PackageInfo packageInfo; + @Nullable public final PackageInfo packageInfo; public final int pmToken; // in post-install restore, the PM's token for this transaction public final boolean isSystemRestore; - public final String[] filterSet; + @Nullable public final String[] filterSet; public final OnTaskFinishedListener listener; /** @@ -129,10 +130,10 @@ public class RestoreParams { IRestoreObserver observer, IBackupManagerMonitor monitor, long token, - PackageInfo packageInfo, + @Nullable PackageInfo packageInfo, int pmToken, boolean isSystemRestore, - String[] filterSet, + @Nullable String[] filterSet, OnTaskFinishedListener listener) { this.transportClient = transportClient; this.observer = observer; diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java index 238f7a05877dd..140dded1cb749 100644 --- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java +++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java @@ -22,6 +22,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSI import static com.android.server.backup.internal.BackupHandler.MSG_RUN_GET_RESTORE_SETS; import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE; +import android.annotation.Nullable; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; @@ -53,13 +54,15 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { private final TransportManager mTransportManager; private final String mTransportName; private final BackupManagerService mBackupManagerService; - private final String mPackageName; + @Nullable private final String mPackageName; public RestoreSet[] mRestoreSets = null; boolean mEnded = false; boolean mTimedOut = false; - public ActiveRestoreSession(BackupManagerService backupManagerService, - String packageName, String transportName) { + public ActiveRestoreSession( + BackupManagerService backupManagerService, + @Nullable String packageName, + String transportName) { mBackupManagerService = backupManagerService; mPackageName = packageName; mTransportManager = backupManagerService.getTransportManager(); @@ -360,6 +363,10 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { } } + public void setRestoreSets(RestoreSet[] restoreSets) { + mRestoreSets = restoreSets; + } + /** * Returns 0 if operation sent or -1 otherwise. */ diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index 6eb9619b8844e..3caa1e7fab796 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -30,6 +30,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTOR import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT; import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT; +import android.annotation.Nullable; import android.app.ApplicationThreadConstants; import android.app.IBackupAgent; import android.app.backup.BackupDataInput; @@ -158,12 +159,18 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { private final int mEphemeralOpToken; - // Invariant: mWakelock is already held, and this task is responsible for - // releasing it at the end of the restore operation. - public PerformUnifiedRestoreTask(BackupManagerService backupManagerService, - TransportClient transportClient, IRestoreObserver observer, - IBackupManagerMonitor monitor, long restoreSetToken, PackageInfo targetPackage, - int pmToken, boolean isFullSystemRestore, String[] filterSet, + // This task can assume that the wakelock is properly held for it and doesn't have to worry + // about releasing it. + public PerformUnifiedRestoreTask( + BackupManagerService backupManagerService, + TransportClient transportClient, + IRestoreObserver observer, + IBackupManagerMonitor monitor, + long restoreSetToken, + @Nullable PackageInfo targetPackage, + int pmToken, + boolean isFullSystemRestore, + @Nullable String[] filterSet, OnTaskFinishedListener listener) { this.backupManagerService = backupManagerService; mTransportManager = backupManagerService.getTransportManager(); @@ -336,7 +343,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { * * [ state change => FINAL ] * - * 7. t.finishRestore(), release wakelock, etc. + * 7. t.finishRestore(), call listeners, etc. * * */ diff --git a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java index 4ac00f0de9be1..c6a4f57b2a917 100644 --- a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java +++ b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java @@ -25,8 +25,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -39,6 +41,7 @@ import android.app.backup.IRestoreSession; import android.app.backup.RestoreSet; import android.os.Looper; import android.os.PowerManager; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import com.android.server.EventLogTags; @@ -51,7 +54,9 @@ import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.ShadowEventLog; +import com.android.server.testing.shadows.ShadowPerformUnifiedRestoreTask; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -62,8 +67,14 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowApplication; import org.robolectric.shadows.ShadowLooper; +import java.util.ArrayDeque; + @RunWith(FrameworkRobolectricTestRunner.class) -@Config(manifest = Config.NONE, sdk = 26, shadows = ShadowEventLog.class) +@Config( + manifest = Config.NONE, + sdk = 26, + shadows = {ShadowEventLog.class, ShadowPerformUnifiedRestoreTask.class} +) @SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class ActiveRestoreSessionTest { @@ -78,6 +89,8 @@ public class ActiveRestoreSessionTest { private ShadowApplication mShadowApplication; private PowerManager.WakeLock mWakeLock; private TransportData mTransport; + private long mToken1; + private long mToken2; private RestoreSet mRestoreSet1; private RestoreSet mRestoreSet2; @@ -87,8 +100,10 @@ public class ActiveRestoreSessionTest { mTransport = backupTransport(); - mRestoreSet1 = new RestoreSet("name1", "device1", 1L); - mRestoreSet2 = new RestoreSet("name2", "device2", 2L); + mToken1 = 1L; + mRestoreSet1 = new RestoreSet("name1", "device1", mToken1); + mToken2 = 2L; + mRestoreSet2 = new RestoreSet("name2", "device2", mToken2); Application application = RuntimeEnvironment.application; mShadowApplication = shadowOf(application); @@ -106,6 +121,12 @@ public class ActiveRestoreSessionTest { application.getPackageManager(), backupHandler, mWakeLock); + when(mBackupManagerService.getPendingRestores()).thenReturn(new ArrayDeque<>()); + } + + @After + public void tearDown() throws Exception { + ShadowPerformUnifiedRestoreTask.reset(); } @Test @@ -193,12 +214,244 @@ public class ActiveRestoreSessionTest { assertThat(mWakeLock.isHeld()).isFalse(); } + @Test + public void testRestoreAll() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + doCallRealMethod().when(mBackupManagerService).setRestoreInProgress(anyBoolean()); + when(mBackupManagerService.isRestoreInProgress()).thenCallRealMethod(); + TransportMock transportMock = setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(0); + verify(mTransportManager) + .disposeOfTransportClient(eq(transportMock.transportClient), any()); + assertThat(mWakeLock.isHeld()).isFalse(); + assertThat(mBackupManagerService.isRestoreInProgress()).isFalse(); + // Verify it created the task properly + ShadowPerformUnifiedRestoreTask shadowTask = + ShadowPerformUnifiedRestoreTask.getLastCreated(); + assertThat(shadowTask.isFullSystemRestore()).isTrue(); + assertThat(shadowTask.getFilterSet()).isNull(); + assertThat(shadowTask.getPackage()).isNull(); + } + + @Test + public void testRestoreAll_whenNoRestoreSets() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = createActiveRestoreSession(null, mTransport); + + int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(-1); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull(); + } + + @Test + public void testRestoreAll_whenSinglePackageSession() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(PACKAGE_1, mTransport, mRestoreSet1); + + int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(-1); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull(); + } + + @Test + public void testRestoreAll_whenSessionEnded() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + restoreSession.endRestoreSession(); + mShadowBackupLooper.runToEndOfTasks(); + + expectThrows( + IllegalStateException.class, + () -> restoreSession.restoreAll(mToken1, mObserver, mMonitor)); + } + + @Test + public void testRestoreAll_whenTransportNotRegistered() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport.unregistered()); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(-1); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull(); + } + + @Test + public void testRestoreAll_whenRestoreInProgress_addsToPendingRestores() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + when(mBackupManagerService.isRestoreInProgress()).thenReturn(true); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + int result = restoreSession.restoreAll(mToken1, mObserver, mMonitor); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(0); + assertThat(mBackupManagerService.getPendingRestores()).hasSize(1); + } + + @Test + public void testRestoreSome_for2Packages() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + TransportMock transportMock = setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + int result = + restoreSession.restoreSome( + mToken1, mObserver, mMonitor, new String[] {PACKAGE_1, PACKAGE_2}); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(0); + verify(mTransportManager) + .disposeOfTransportClient(eq(transportMock.transportClient), any()); + assertThat(mWakeLock.isHeld()).isFalse(); + assertThat(mBackupManagerService.isRestoreInProgress()).isFalse(); + ShadowPerformUnifiedRestoreTask shadowTask = + ShadowPerformUnifiedRestoreTask.getLastCreated(); + assertThat(shadowTask.getFilterSet()).asList().containsExactly(PACKAGE_1, PACKAGE_2); + assertThat(shadowTask.getPackage()).isNull(); + } + + @Test + public void testRestoreSome_for2Packages_createsSystemRestoreTask() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + restoreSession.restoreSome( + mToken1, mObserver, mMonitor, new String[] {PACKAGE_1, PACKAGE_2}); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated().isFullSystemRestore()).isTrue(); + } + + @Test + public void testRestoreSome_for1Package() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1}); + + mShadowBackupLooper.runToEndOfTasks(); + ShadowPerformUnifiedRestoreTask shadowTask = + ShadowPerformUnifiedRestoreTask.getLastCreated(); + assertThat(shadowTask.getFilterSet()).asList().containsExactly(PACKAGE_1); + assertThat(shadowTask.getPackage()).isNull(); + } + + @Test + public void testRestoreSome_for1Package_createsNonSystemRestoreTask() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1}); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated().isFullSystemRestore()) + .isFalse(); + } + + @Test + public void testRestoreSome_whenNoRestoreSets() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = createActiveRestoreSession(null, mTransport); + + int result = + restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1}); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(-1); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull(); + } + + @Test + public void testRestoreSome_whenSinglePackageSession() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(PACKAGE_1, mTransport, mRestoreSet1); + + int result = + restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_2}); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(-1); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull(); + } + + @Test + public void testRestoreSome_whenSessionEnded() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + restoreSession.endRestoreSession(); + mShadowBackupLooper.runToEndOfTasks(); + + expectThrows( + IllegalStateException.class, + () -> + restoreSession.restoreSome( + mToken1, mObserver, mMonitor, new String[] {PACKAGE_1})); + } + + @Test + public void testRestoreSome_whenTransportNotRegistered() throws Exception { + mShadowApplication.grantPermissions(android.Manifest.permission.BACKUP); + setUpTransport(mTransport.unregistered()); + IRestoreSession restoreSession = + createActiveRestoreSessionWithRestoreSets(null, mTransport, mRestoreSet1); + + int result = + restoreSession.restoreSome(mToken1, mObserver, mMonitor, new String[] {PACKAGE_1}); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(result).isEqualTo(-1); + assertThat(ShadowPerformUnifiedRestoreTask.getLastCreated()).isNull(); + } + private IRestoreSession createActiveRestoreSession( String packageName, TransportData transport) { return new ActiveRestoreSession( mBackupManagerService, packageName, transport.transportName); } + private IRestoreSession createActiveRestoreSessionWithRestoreSets( + String packageName, TransportData transport, RestoreSet... restoreSets) + throws RemoteException { + ActiveRestoreSession restoreSession = + new ActiveRestoreSession( + mBackupManagerService, packageName, transport.transportName); + restoreSession.setRestoreSets(restoreSets); + return restoreSession; + } + private TransportMock setUpTransport(TransportData transport) throws Exception { return TransportTestUtils.setUpTransport(mTransportManager, transport); } diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java index 565c7e638aacd..c00a61dde90a8 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java @@ -115,6 +115,7 @@ public class TransportTestUtils { .thenReturn(transportDirName); when(transportManager.getTransportDirName(eq(transportComponent))) .thenReturn(transportDirName); + when(transportManager.isTransportRegistered(eq(transportName))).thenReturn(true); // TODO: Mock rest of description methods } else { // Transport not registered @@ -127,6 +128,7 @@ public class TransportTestUtils { .thenThrow(TransportNotRegisteredException.class); when(transportManager.getTransportDirName(eq(transportComponent))) .thenThrow(TransportNotRegisteredException.class); + when(transportManager.isTransportRegistered(eq(transportName))).thenReturn(false); } return transportMock; } diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java new file mode 100644 index 0000000000000..0f93c7a1d0b30 --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 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.testing.shadows; + +import android.annotation.Nullable; +import android.app.backup.IBackupManagerMonitor; +import android.app.backup.IRestoreObserver; +import android.content.pm.PackageInfo; + +import com.android.server.backup.BackupManagerService; +import com.android.server.backup.internal.OnTaskFinishedListener; +import com.android.server.backup.restore.PerformUnifiedRestoreTask; +import com.android.server.backup.transport.TransportClient; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(PerformUnifiedRestoreTask.class) +public class ShadowPerformUnifiedRestoreTask { + @Nullable private static ShadowPerformUnifiedRestoreTask sLastShadow; + + /** + * Retrieves the shadow for the last {@link PerformUnifiedRestoreTask} object created. + * + * @return The shadow or {@code null} if no object created since last {@link #reset()}. + */ + @Nullable + public static ShadowPerformUnifiedRestoreTask getLastCreated() { + return sLastShadow; + } + + public static void reset() { + sLastShadow = null; + } + + private BackupManagerService mBackupManagerService; + @Nullable private PackageInfo mPackage; + private boolean mIsFullSystemRestore; + @Nullable private String[] mFilterSet; + private OnTaskFinishedListener mListener; + + @Implementation + public void __constructor__( + BackupManagerService backupManagerService, + TransportClient transportClient, + IRestoreObserver observer, + IBackupManagerMonitor monitor, + long restoreSetToken, + @Nullable PackageInfo targetPackage, + int pmToken, + boolean isFullSystemRestore, + @Nullable String[] filterSet, + OnTaskFinishedListener listener) { + mBackupManagerService = backupManagerService; + mPackage = targetPackage; + mIsFullSystemRestore = isFullSystemRestore; + mFilterSet = filterSet; + mListener = listener; + sLastShadow = this; + } + + @Implementation + public void execute() { + mBackupManagerService.setRestoreInProgress(false); + mListener.onFinished("ShadowPerformUnifiedRestoreTask.execute()"); + } + + public PackageInfo getPackage() { + return mPackage; + } + + public String[] getFilterSet() { + return mFilterSet; + } + + public boolean isFullSystemRestore() { + return mIsFullSystemRestore; + } +}