Merge "Partially delete unused EITF code"
This commit is contained in:
committed by
Android (Google) Code Review
commit
aa6380d616
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.encryption;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.encryption.transport.IntermediateEncryptingTransport;
|
||||
import com.android.server.backup.encryption.transport.IntermediateEncryptingTransportManager;
|
||||
|
||||
/**
|
||||
* This service provides encryption of backup data. For an intent used to bind to this service, it
|
||||
* provides an {@link IntermediateEncryptingTransport} which is an implementation of {@link
|
||||
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from the
|
||||
* real {@link IBackupTransport}.
|
||||
*/
|
||||
public class BackupEncryptionService extends Service {
|
||||
public static final String TAG = "BackupEncryption";
|
||||
private static IntermediateEncryptingTransportManager sTransportManager = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.i(TAG, "onCreate:" + this);
|
||||
if (sTransportManager == null) {
|
||||
Log.i(TAG, "Creating IntermediateEncryptingTransportManager");
|
||||
sTransportManager = new IntermediateEncryptingTransportManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.i(TAG, "onDestroy:" + this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// TODO (b141536117): Check connection with TransportClient.connect and return null on fail.
|
||||
return sTransportManager.get(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
sTransportManager.cleanup(intent);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.server.backup.encryption;
|
||||
|
||||
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
@@ -36,6 +34,8 @@ import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
|
||||
public class KeyValueEncrypter {
|
||||
private static final String TAG = "KeyValueEncrypter";
|
||||
|
||||
private final Context mContext;
|
||||
private final EncryptionKeyHelper mKeyHelper;
|
||||
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.encryption.transport;
|
||||
|
||||
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
|
||||
|
||||
import android.app.backup.BackupTransport;
|
||||
import android.app.backup.RestoreDescription;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.encryption.KeyValueEncrypter;
|
||||
import com.android.server.backup.transport.DelegatingTransport;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
|
||||
* sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
|
||||
* TransportClient.connect(String)}.
|
||||
*/
|
||||
public class IntermediateEncryptingTransport extends DelegatingTransport {
|
||||
private static final String BACKUP_TEMP_DIR = "backup";
|
||||
private static final String RESTORE_TEMP_DIR = "restore";
|
||||
|
||||
private final TransportClient mTransportClient;
|
||||
private final Object mConnectLock = new Object();
|
||||
private final Context mContext;
|
||||
private volatile IBackupTransport mRealTransport;
|
||||
private AtomicReference<String> mNextRestorePackage = new AtomicReference<>();
|
||||
private final KeyValueEncrypter mKeyValueEncrypter;
|
||||
private final boolean mShouldEncrypt;
|
||||
|
||||
IntermediateEncryptingTransport(
|
||||
TransportClient transportClient, Context context, boolean shouldEncrypt) {
|
||||
this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
IntermediateEncryptingTransport(
|
||||
TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
|
||||
boolean shouldEncrypt) {
|
||||
mTransportClient = transportClient;
|
||||
mContext = context;
|
||||
mKeyValueEncrypter = keyValueEncrypter;
|
||||
mShouldEncrypt = shouldEncrypt;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IBackupTransport getDelegate() throws RemoteException {
|
||||
if (mRealTransport == null) {
|
||||
connect();
|
||||
}
|
||||
Log.d(TAG, "real transport = " + mRealTransport.name());
|
||||
return mRealTransport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
|
||||
throws RemoteException {
|
||||
if (!mShouldEncrypt) {
|
||||
return super.performBackup(packageInfo, inFd, flags);
|
||||
}
|
||||
|
||||
File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName);
|
||||
if (encryptedStorageFile == null) {
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
// Encrypt the backup data and write it into a temp file.
|
||||
try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) {
|
||||
mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd,
|
||||
encryptedOutput);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to encrypt backup data: ", e);
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
// Pass the temp file to the real transport for backup.
|
||||
try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) {
|
||||
return super.performBackup(
|
||||
packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to read encrypted data from temp storage: ", e);
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
|
||||
if (!mShouldEncrypt) {
|
||||
return super.getRestoreData(outFd);
|
||||
}
|
||||
|
||||
String nextRestorePackage = mNextRestorePackage.get();
|
||||
if (nextRestorePackage == null) {
|
||||
Log.e(TAG, "No next restore package set");
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage);
|
||||
if (encryptedStorageFile == null) {
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
// Get encrypted restore data from the real transport and write it into a temp file.
|
||||
try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) {
|
||||
int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD()));
|
||||
if (status != BackupTransport.TRANSPORT_OK) {
|
||||
Log.e(TAG, "Failed to read restore data from transport, status = " + status);
|
||||
return status;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to write encrypted data to temp storage: ", e);
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
// Decrypt the data and write it into the fd given by the real transport.
|
||||
try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) {
|
||||
mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd);
|
||||
encryptedStorageFile.delete();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to decrypt restored data: ", e);
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
return BackupTransport.TRANSPORT_OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestoreDescription nextRestorePackage() throws RemoteException {
|
||||
if (!mShouldEncrypt) {
|
||||
return super.nextRestorePackage();
|
||||
}
|
||||
|
||||
RestoreDescription restoreDescription = super.nextRestorePackage();
|
||||
mNextRestorePackage.set(restoreDescription.getPackageName());
|
||||
|
||||
return restoreDescription;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected File getBackupTempStorage(String packageName) {
|
||||
return getTempStorage(packageName, BACKUP_TEMP_DIR);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected File getRestoreTempStorage(String packageName) {
|
||||
return getTempStorage(packageName, RESTORE_TEMP_DIR);
|
||||
}
|
||||
|
||||
private File getTempStorage(String packageName, String operationType) {
|
||||
File encryptedDir = new File(mContext.getFilesDir(), operationType);
|
||||
encryptedDir.mkdir();
|
||||
File encryptedFile = new File(encryptedDir, packageName);
|
||||
try {
|
||||
encryptedFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to create temp file for encrypted data: ", e);
|
||||
}
|
||||
return encryptedFile;
|
||||
}
|
||||
|
||||
private void connect() throws RemoteException {
|
||||
Log.i(TAG, "connecting " + mTransportClient);
|
||||
synchronized (mConnectLock) {
|
||||
if (mRealTransport == null) {
|
||||
mRealTransport = mTransportClient.connect("IntermediateEncryptingTransport");
|
||||
if (mRealTransport == null) {
|
||||
throw new RemoteException("Could not connect: " + mTransportClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TransportClient getClient() {
|
||||
return mTransportClient;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setNextRestorePackage(String nextRestorePackage) {
|
||||
mNextRestorePackage.set(nextRestorePackage);
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.encryption.transport;
|
||||
|
||||
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.server.backup.transport.TransportClientManager;
|
||||
import com.android.server.backup.transport.TransportStats;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances. */
|
||||
public class IntermediateEncryptingTransportManager {
|
||||
private static final String CALLER = "IntermediateEncryptingTransportManager";
|
||||
private final TransportClientManager mTransportClientManager;
|
||||
private final Object mTransportsLock = new Object();
|
||||
private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
|
||||
private Context mContext;
|
||||
|
||||
@VisibleForTesting
|
||||
IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
|
||||
mTransportClientManager = transportClientManager;
|
||||
}
|
||||
|
||||
public IntermediateEncryptingTransportManager(Context context) {
|
||||
this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the {@link ComponentName} corresponding to the real {@link IBackupTransport}, and
|
||||
* provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
|
||||
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
|
||||
* the real {@link IBackupTransport}.
|
||||
*
|
||||
* @param intent {@link Intent} created with a call to {@link
|
||||
* TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
|
||||
* @return
|
||||
*/
|
||||
public IntermediateEncryptingTransport get(Intent intent) {
|
||||
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
|
||||
Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
|
||||
synchronized (mTransportsLock) {
|
||||
return mTransports.computeIfAbsent(
|
||||
transportIntent.getComponent(), c -> create(transportIntent));
|
||||
}
|
||||
}
|
||||
|
||||
/** Create an instance of {@link IntermediateEncryptingTransport}. */
|
||||
private IntermediateEncryptingTransport create(Intent realTransportIntent) {
|
||||
Log.d(TAG, "create: intent:" + realTransportIntent);
|
||||
|
||||
LockPatternUtils patternUtils = new LockPatternUtils(mContext);
|
||||
boolean shouldEncrypt =
|
||||
realTransportIntent.getComponent().getClassName().contains("EncryptedLocalTransport")
|
||||
&& (patternUtils.isLockPatternEnabled(UserHandle.myUserId())
|
||||
|| patternUtils.isLockPasswordEnabled(UserHandle.myUserId()));
|
||||
|
||||
return new IntermediateEncryptingTransport(
|
||||
mTransportClientManager.getTransportClient(
|
||||
realTransportIntent.getComponent(),
|
||||
realTransportIntent.getExtras(),
|
||||
CALLER),
|
||||
mContext,
|
||||
shouldEncrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to {@link
|
||||
* #get(Intent)} with this {@link Intent}.
|
||||
*/
|
||||
public void cleanup(Intent intent) {
|
||||
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
|
||||
Log.i(TAG, "cleanup: intent:" + intent + " transportIntent:" + transportIntent);
|
||||
|
||||
IntermediateEncryptingTransport transport;
|
||||
synchronized (mTransportsLock) {
|
||||
transport = mTransports.remove(transportIntent.getComponent());
|
||||
}
|
||||
if (transport != null) {
|
||||
mTransportClientManager.disposeOfTransportClient(transport.getClient(), CALLER);
|
||||
} else {
|
||||
Log.i(TAG, "Could not find IntermediateEncryptingTransport");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.encryption.transport;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotSame;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
import com.android.server.backup.transport.TransportClientManager;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IntermediateEncryptingTransportManagerTest {
|
||||
@Mock private TransportClient mTransportClient;
|
||||
@Mock private TransportClientManager mTransportClientManager;
|
||||
|
||||
private final ComponentName mTransportComponent = new ComponentName("pkg", "class");
|
||||
private final Bundle mExtras = new Bundle();
|
||||
private Intent mEncryptingTransportIntent;
|
||||
private IntermediateEncryptingTransportManager mIntermediateEncryptingTransportManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mExtras.putInt("test", 1);
|
||||
mEncryptingTransportIntent =
|
||||
TransportClientManager.getEncryptingTransportIntent(mTransportComponent)
|
||||
.putExtras(mExtras);
|
||||
mIntermediateEncryptingTransportManager =
|
||||
new IntermediateEncryptingTransportManager(mTransportClientManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_createsClientWithRealTransportComponentAndExtras() {
|
||||
when(mTransportClientManager.getTransportClient(any(), any(), any()))
|
||||
.thenReturn(mTransportClient);
|
||||
|
||||
IntermediateEncryptingTransport intermediateEncryptingTransport =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
|
||||
assertEquals(mTransportClient, intermediateEncryptingTransport.getClient());
|
||||
verify(mTransportClientManager, times(1))
|
||||
.getTransportClient(eq(mTransportComponent), argThat(mExtras::kindofEquals), any());
|
||||
verifyNoMoreInteractions(mTransportClientManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet_callTwice_returnsSameTransport() {
|
||||
IntermediateEncryptingTransport transport1 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
IntermediateEncryptingTransport transport2 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
|
||||
assertEquals(transport1, transport2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanup_disposesTransportClient() {
|
||||
when(mTransportClientManager.getTransportClient(any(), any(), any()))
|
||||
.thenReturn(mTransportClient);
|
||||
|
||||
IntermediateEncryptingTransport transport =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
|
||||
|
||||
verify(mTransportClientManager, times(1)).getTransportClient(any(), any(), any());
|
||||
verify(mTransportClientManager, times(1))
|
||||
.disposeOfTransportClient(eq(mTransportClient), any());
|
||||
verifyNoMoreInteractions(mTransportClientManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCleanup_removesCachedTransport() {
|
||||
when(mTransportClientManager.getTransportClient(any(), any(), any()))
|
||||
.thenReturn(mTransportClient);
|
||||
|
||||
IntermediateEncryptingTransport transport1 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
|
||||
IntermediateEncryptingTransport transport2 =
|
||||
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
|
||||
|
||||
assertNotSame(transport1, transport2);
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.encryption.transport;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.backup.BackupTransport;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.encryption.KeyValueEncrypter;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IntermediateEncryptingTransportTest {
|
||||
private static final String TEST_PACKAGE_NAME = "test_package";
|
||||
|
||||
private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
|
||||
private final PackageInfo mTestPackage = new PackageInfo();
|
||||
|
||||
@Mock private IBackupTransport mRealTransport;
|
||||
@Mock private TransportClient mTransportClient;
|
||||
@Mock private ParcelFileDescriptor mParcelFileDescriptor;
|
||||
@Mock private KeyValueEncrypter mKeyValueEncrypter;
|
||||
@Mock private Context mContext;
|
||||
|
||||
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
|
||||
|
||||
private File mTempFile;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mIntermediateEncryptingTransport =
|
||||
new IntermediateEncryptingTransport(
|
||||
mTransportClient, mContext, mKeyValueEncrypter, true);
|
||||
mTestPackage.packageName = TEST_PACKAGE_NAME;
|
||||
mTempFile = mTemporaryFolder.newFile();
|
||||
|
||||
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
|
||||
when(mRealTransport.getRestoreData(any())).thenReturn(BackupTransport.TRANSPORT_OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDelegate_callsConnect() throws Exception {
|
||||
IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
|
||||
|
||||
assertEquals(mRealTransport, ret);
|
||||
verify(mTransportClient, times(1)).connect(anyString());
|
||||
verifyNoMoreInteractions(mTransportClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDelegate_callTwice_callsConnectOnce() throws Exception {
|
||||
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
|
||||
|
||||
IBackupTransport ret1 = mIntermediateEncryptingTransport.getDelegate();
|
||||
IBackupTransport ret2 = mIntermediateEncryptingTransport.getDelegate();
|
||||
|
||||
assertEquals(mRealTransport, ret1);
|
||||
assertEquals(mRealTransport, ret2);
|
||||
verify(mTransportClient, times(1)).connect(anyString());
|
||||
verifyNoMoreInteractions(mTransportClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformBackup_shouldEncryptTrue_encryptsDataAndPassesToDelegate()
|
||||
throws Exception {
|
||||
mIntermediateEncryptingTransport =
|
||||
new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
|
||||
|
||||
mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
|
||||
|
||||
verify(mKeyValueEncrypter, times(1))
|
||||
.encryptKeyValueData(eq(TEST_PACKAGE_NAME), eq(mParcelFileDescriptor), any());
|
||||
verify(mRealTransport, times(1)).performBackup(eq(mTestPackage), any(), eq(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformBackup_shouldEncryptFalse_doesntEncryptDataAndPassedToDelegate()
|
||||
throws Exception {
|
||||
mIntermediateEncryptingTransport =
|
||||
new TestIntermediateTransport(
|
||||
mTransportClient, mContext, mKeyValueEncrypter, false);
|
||||
|
||||
mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
|
||||
|
||||
verifyZeroInteractions(mKeyValueEncrypter);
|
||||
verify(mRealTransport, times(1))
|
||||
.performBackup(eq(mTestPackage), eq(mParcelFileDescriptor), eq(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestoreData_shouldEncryptTrue_decryptsDataAndPassesToDelegate()
|
||||
throws Exception {
|
||||
mIntermediateEncryptingTransport =
|
||||
new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
|
||||
mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
|
||||
|
||||
mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
|
||||
|
||||
verify(mKeyValueEncrypter, times(1))
|
||||
.decryptKeyValueData(eq(TEST_PACKAGE_NAME), any(), eq(mParcelFileDescriptor));
|
||||
verify(mRealTransport, times(1)).getRestoreData(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestoreData_shouldEncryptFalse_doesntDecryptDataAndPassesToDelegate()
|
||||
throws Exception {
|
||||
mIntermediateEncryptingTransport =
|
||||
new TestIntermediateTransport(
|
||||
mTransportClient, mContext, mKeyValueEncrypter, false);
|
||||
mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
|
||||
|
||||
mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
|
||||
|
||||
verifyZeroInteractions(mKeyValueEncrypter);
|
||||
verify(mRealTransport, times(1)).getRestoreData(eq(mParcelFileDescriptor));
|
||||
}
|
||||
|
||||
private final class TestIntermediateTransport extends IntermediateEncryptingTransport {
|
||||
TestIntermediateTransport(
|
||||
TransportClient transportClient,
|
||||
Context context,
|
||||
KeyValueEncrypter keyValueEncrypter,
|
||||
boolean shouldEncrypt) {
|
||||
super(transportClient, context, keyValueEncrypter, shouldEncrypt);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getBackupTempStorage(String packageName) {
|
||||
return mTempFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getRestoreTempStorage(String packageName) {
|
||||
return mTempFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,414 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.transport;
|
||||
|
||||
import android.app.backup.BackupAgent;
|
||||
import android.app.backup.BackupTransport;
|
||||
import android.app.backup.RestoreDescription;
|
||||
import android.app.backup.RestoreSet;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
|
||||
/**
|
||||
* Delegates all transport methods to the delegate() implemented in the derived class.
|
||||
*/
|
||||
public abstract class DelegatingTransport extends IBackupTransport.Stub {
|
||||
protected abstract IBackupTransport getDelegate() throws RemoteException;
|
||||
|
||||
/**
|
||||
* Ask the transport for the name under which it should be registered. This will
|
||||
* typically be its host service's component name, but need not be.
|
||||
*/
|
||||
@Override
|
||||
public String name() throws RemoteException {
|
||||
return getDelegate().name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the transport for an Intent that can be used to launch any internal
|
||||
* configuration Activity that it wishes to present. For example, the transport
|
||||
* may offer a UI for allowing the user to supply login credentials for the
|
||||
* transport's off-device backend.
|
||||
*
|
||||
* If the transport does not supply any user-facing configuration UI, it should
|
||||
* return null from this method.
|
||||
*
|
||||
* @return An Intent that can be passed to Context.startActivity() in order to
|
||||
* launch the transport's configuration UI. This method will return null
|
||||
* if the transport does not offer any user-facing configuration UI.
|
||||
*/
|
||||
@Override
|
||||
public Intent configurationIntent() throws RemoteException {
|
||||
return getDelegate().configurationIntent();
|
||||
}
|
||||
|
||||
/**
|
||||
* On demand, supply a one-line string that can be shown to the user that
|
||||
* describes the current backend destination. For example, a transport that
|
||||
* can potentially associate backup data with arbitrary user accounts should
|
||||
* include the name of the currently-active account here.
|
||||
*
|
||||
* @return A string describing the destination to which the transport is currently
|
||||
* sending data. This method should not return null.
|
||||
*/
|
||||
@Override
|
||||
public String currentDestinationString() throws RemoteException {
|
||||
return getDelegate().currentDestinationString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the transport for an Intent that can be used to launch a more detailed
|
||||
* secondary data management activity. For example, the configuration intent might
|
||||
* be one for allowing the user to select which account they wish to associate
|
||||
* their backups with, and the management intent might be one which presents a
|
||||
* UI for managing the data on the backend.
|
||||
*
|
||||
* <p>In the Settings UI, the configuration intent will typically be invoked
|
||||
* when the user taps on the preferences item labeled with the current
|
||||
* destination string, and the management intent will be placed in an overflow
|
||||
* menu labelled with the management label string.
|
||||
*
|
||||
* <p>If the transport does not supply any user-facing data management
|
||||
* UI, then it should return {@code null} from this method.
|
||||
*
|
||||
* @return An intent that can be passed to Context.startActivity() in order to
|
||||
* launch the transport's data-management UI. This method will return
|
||||
* {@code null} if the transport does not offer any user-facing data
|
||||
* management UI.
|
||||
*/
|
||||
@Override
|
||||
public Intent dataManagementIntent() throws RemoteException {
|
||||
return getDelegate().dataManagementIntent();
|
||||
}
|
||||
|
||||
/**
|
||||
* On demand, supply a short {@link CharSequence} that can be shown to the user as the
|
||||
* label on
|
||||
* an overflow menu item used to invoke the data management UI.
|
||||
*
|
||||
* @return A {@link CharSequence} to be used as the label for the transport's data management
|
||||
* affordance. If the transport supplies a data management intent, this
|
||||
* method must not return {@code null}.
|
||||
*/
|
||||
@Override
|
||||
public CharSequence dataManagementIntentLabel() throws RemoteException {
|
||||
return getDelegate().dataManagementIntentLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the transport where, on local device storage, to keep backup state blobs.
|
||||
* This is per-transport so that mock transports used for testing can coexist with
|
||||
* "live" backup services without interfering with the live bookkeeping. The
|
||||
* returned string should be a name that is expected to be unambiguous among all
|
||||
* available backup transports; the name of the class implementing the transport
|
||||
* is a good choice. This MUST be constant.
|
||||
*
|
||||
* @return A unique name, suitable for use as a file or directory name, that the
|
||||
* Backup Manager could use to disambiguate state files associated with
|
||||
* different backup transports.
|
||||
*/
|
||||
@Override
|
||||
public String transportDirName() throws RemoteException {
|
||||
return getDelegate().transportDirName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that this is a suitable time for a backup pass. This should return zero
|
||||
* if a backup is reasonable right now, some positive value otherwise. This method
|
||||
* will be called outside of the {@link #startSession}/{@link #endSession} pair.
|
||||
*
|
||||
* <p>If this is not a suitable time for a backup, the transport should return a
|
||||
* backoff delay, in milliseconds, after which the Backup Manager should try again.
|
||||
*
|
||||
* @return Zero if this is a suitable time for a backup pass, or a positive time delay
|
||||
* in milliseconds to suggest deferring the backup pass for a while.
|
||||
*/
|
||||
@Override
|
||||
public long requestBackupTime() throws RemoteException {
|
||||
return getDelegate().requestBackupTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the server side storage for this device, erasing all stored data.
|
||||
* The transport may send the request immediately, or may buffer it. After
|
||||
* this is called, {@link #finishBackup} must be called to ensure the request
|
||||
* is sent and received successfully.
|
||||
*
|
||||
* @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
|
||||
* {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
|
||||
*/
|
||||
@Override
|
||||
public int initializeDevice() throws RemoteException {
|
||||
return getDelegate().initializeDevice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send one application's data to the backup destination. The transport may send
|
||||
* the data immediately, or may buffer it. After this is called, {@link #finishBackup}
|
||||
* must be called to ensure the data is sent and recorded successfully.
|
||||
*
|
||||
* @param packageInfo The identity of the application whose data is being backed up.
|
||||
* This specifically includes the signature list for the package.
|
||||
* @param inFd Descriptor of file with data that resulted from invoking the application's
|
||||
* BackupService.doBackup() method. This may be a pipe rather than a file on
|
||||
* persistent media, so it may not be seekable.
|
||||
* @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}.
|
||||
* @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
|
||||
* {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
|
||||
* {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
|
||||
* become lost due to inactive expiry or some other reason and needs re-initializing)
|
||||
*/
|
||||
@Override
|
||||
public int performBackup(PackageInfo packageInfo,
|
||||
ParcelFileDescriptor inFd, int flags) throws RemoteException {
|
||||
return getDelegate().performBackup(packageInfo, inFd, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase the give application's data from the backup destination. This clears
|
||||
* out the given package's data from the current backup set, making it as though
|
||||
* the app had never yet been backed up. After this is called, {@link finishBackup}
|
||||
* must be called to ensure that the operation is recorded successfully.
|
||||
*
|
||||
* @return the same error codes as {@link #performBackup}.
|
||||
* @param packageInfo
|
||||
*/
|
||||
@Override
|
||||
public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
|
||||
return getDelegate().clearBackupData(packageInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish sending application data to the backup destination. This must be
|
||||
* called after {@link #performBackup} or {@link clearBackupData} to ensure that
|
||||
* all data is sent. Only when this method returns true can a backup be assumed
|
||||
* to have succeeded.
|
||||
*
|
||||
* @return the same error codes as {@link #performBackup}.
|
||||
*/
|
||||
@Override
|
||||
public int finishBackup() throws RemoteException {
|
||||
return getDelegate().finishBackup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of all backups currently available over this transport.
|
||||
*
|
||||
* @return Descriptions of the set of restore images available for this device,
|
||||
* or null if an error occurred (the attempt should be rescheduled).
|
||||
**/
|
||||
@Override
|
||||
public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
|
||||
return getDelegate().getAvailableRestoreSets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifying token of the backup set currently being stored from
|
||||
* this device. This is used in the case of applications wishing to restore
|
||||
* their last-known-good data.
|
||||
*
|
||||
* @return A token that can be passed to {@link #startRestore}, or 0 if there
|
||||
* is no backup set available corresponding to the current device state.
|
||||
*/
|
||||
@Override
|
||||
public long getCurrentRestoreSet() throws RemoteException {
|
||||
return getDelegate().getCurrentRestoreSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start restoring application data from backup. After calling this function,
|
||||
* alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
|
||||
* to walk through the actual application data.
|
||||
*
|
||||
* @param token A backup token as returned by {@link #getAvailableRestoreSets}
|
||||
* or {@link #getCurrentRestoreSet}.
|
||||
* @param packages List of applications to restore (if data is available).
|
||||
* Application data will be restored in the order given.
|
||||
* @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
|
||||
* {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR}
|
||||
* (an error occurred, the restore should be aborted and rescheduled).
|
||||
*/
|
||||
@Override
|
||||
public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
|
||||
return getDelegate().startRestore(token, packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the package name of the next application with data in the backup store, plus
|
||||
* a description of the structure of the restored archive: either TYPE_KEY_VALUE for
|
||||
* an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream.
|
||||
*
|
||||
* <p>If the package name in the returned RestoreDescription object is the singleton
|
||||
* {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available
|
||||
* in the current restore session: all packages described in startRestore() have been
|
||||
* processed.
|
||||
*
|
||||
* <p>If this method returns {@code null}, it means that a transport-level error has
|
||||
* occurred and the entire restore operation should be abandoned.
|
||||
*
|
||||
* @return A RestoreDescription object containing the name of one of the packages
|
||||
* supplied to {@link #startRestore} plus an indicator of the data type of that
|
||||
* restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that
|
||||
* no more packages can be restored in this session; or {@code null} to indicate
|
||||
* a transport-level error.
|
||||
*/
|
||||
@Override
|
||||
public RestoreDescription nextRestorePackage() throws RemoteException {
|
||||
return getDelegate().nextRestorePackage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data for the application returned by {@link #nextRestorePackage}.
|
||||
*
|
||||
* @param outFd An open, writable file into which the backup data should be stored.
|
||||
* @return the same error codes as {@link #startRestore}.
|
||||
*/
|
||||
@Override
|
||||
public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
|
||||
return getDelegate().getRestoreData(outFd);
|
||||
}
|
||||
|
||||
/**
|
||||
* End a restore session (aborting any in-process data transfer as necessary),
|
||||
* freeing any resources and connections used during the restore process.
|
||||
*/
|
||||
@Override
|
||||
public void finishRestore() throws RemoteException {
|
||||
getDelegate().finishRestore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long requestFullBackupTime() throws RemoteException {
|
||||
return getDelegate().requestFullBackupTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int performFullBackup(PackageInfo targetPackage,
|
||||
ParcelFileDescriptor socket, int flags) throws RemoteException {
|
||||
return getDelegate().performFullBackup(targetPackage, socket, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int checkFullBackupSize(long size) throws RemoteException {
|
||||
return getDelegate().checkFullBackupSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendBackupData(int numBytes) throws RemoteException {
|
||||
return getDelegate().sendBackupData(numBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelFullBackup() throws RemoteException {
|
||||
getDelegate().cancelFullBackup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the transport whether this app is eligible for backup.
|
||||
*
|
||||
* @param targetPackage The identity of the application.
|
||||
* @param isFullBackup If set, transport should check if app is eligible for full data backup,
|
||||
* otherwise to check if eligible for key-value backup.
|
||||
* @return Whether this app is eligible for backup.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAppEligibleForBackup(PackageInfo targetPackage,
|
||||
boolean isFullBackup) throws RemoteException {
|
||||
return getDelegate().isAppEligibleForBackup(targetPackage, isFullBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the transport about current quota for backup size of the package.
|
||||
*
|
||||
* @param packageName ID of package to provide the quota.
|
||||
* @param isFullBackup If set, transport should return limit for full data backup, otherwise
|
||||
* for key-value backup.
|
||||
* @return Current limit on full data backup size in bytes.
|
||||
*/
|
||||
@Override
|
||||
public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
|
||||
return getDelegate().getBackupQuota(packageName, isFullBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the transport to provide data for the "current" package being restored. This
|
||||
* is the package that was just reported by {@link #nextRestorePackage()} as having
|
||||
* {@link RestoreDescription#TYPE_FULL_STREAM} data.
|
||||
*
|
||||
* The transport writes some data to the socket supplied to this call, and returns
|
||||
* the number of bytes written. The system will then read that many bytes and
|
||||
* stream them to the application's agent for restore, then will call this method again
|
||||
* to receive the next chunk of the archive. This sequence will be repeated until the
|
||||
* transport returns zero indicating that all of the package's data has been delivered
|
||||
* (or returns a negative value indicating some sort of hard error condition at the
|
||||
* transport level).
|
||||
*
|
||||
* <p>After this method returns zero, the system will then call
|
||||
* {@link #getNextFullRestorePackage()} to begin the restore process for the next
|
||||
* application, and the sequence begins again.
|
||||
*
|
||||
* <p>The transport should always close this socket when returning from this method.
|
||||
* Do not cache this socket across multiple calls or you may leak file descriptors.
|
||||
*
|
||||
* @param socket The file descriptor that the transport will use for delivering the
|
||||
* streamed archive. The transport must close this socket in all cases when returning
|
||||
* from this method.
|
||||
* @return 0 when no more data for the current package is available. A positive value
|
||||
* indicates the presence of that many bytes to be delivered to the app. Any negative
|
||||
* return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
|
||||
* indicating a fatal error condition that precludes further restore operations
|
||||
* on the current dataset.
|
||||
*/
|
||||
@Override
|
||||
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException {
|
||||
return getDelegate().getNextFullRestoreDataChunk(socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
|
||||
* data for restore, it will invoke this method to tell the transport that it should
|
||||
* abandon the data download for the current package. The OS will then either call
|
||||
* {@link #nextRestorePackage()} again to move on to restoring the next package in the
|
||||
* set being iterated over, or will call {@link #finishRestore()} to shut down the restore
|
||||
* operation.
|
||||
*
|
||||
* @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
|
||||
* current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
|
||||
* transport-level failure. If the transport reports an error here, the entire restore
|
||||
* operation will immediately be finished with no further attempts to restore app data.
|
||||
*/
|
||||
@Override
|
||||
public int abortFullRestore() throws RemoteException {
|
||||
return getDelegate().abortFullRestore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns flags with additional information about the transport, which is accessible to the
|
||||
* {@link BackupAgent}. This allows the agent to decide what to backup or
|
||||
* restore based on properties of the transport.
|
||||
*
|
||||
* <p>For supported flags see {@link BackupAgent}.
|
||||
*/
|
||||
@Override
|
||||
public int getTransportFlags() throws RemoteException {
|
||||
return getDelegate().getTransportFlags();
|
||||
}
|
||||
}
|
||||
@@ -41,12 +41,6 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class TransportClientManager {
|
||||
private static final String TAG = "TransportClientManager";
|
||||
private static final String SERVICE_ACTION_ENCRYPTING_TRANSPORT =
|
||||
"android.encryption.BACKUP_ENCRYPTION";
|
||||
private static final ComponentName ENCRYPTING_TRANSPORT = new ComponentName(
|
||||
"com.android.server.backup.encryption",
|
||||
"com.android.server.backup.encryption.BackupEncryptionService");
|
||||
private static final String ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY = "transport";
|
||||
|
||||
private final @UserIdInt int mUserId;
|
||||
private final Context mContext;
|
||||
@@ -56,17 +50,6 @@ public class TransportClientManager {
|
||||
private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>();
|
||||
private final Function<ComponentName, Intent> mIntentFunction;
|
||||
|
||||
/**
|
||||
* Return an {@link Intent} which resolves to an intermediate {@link IBackupTransport} that
|
||||
* encrypts (or decrypts) the data when sending it (or receiving it) from the {@link
|
||||
* IBackupTransport} for the given {@link ComponentName}.
|
||||
*/
|
||||
public static Intent getEncryptingTransportIntent(ComponentName tranportComponent) {
|
||||
return new Intent(SERVICE_ACTION_ENCRYPTING_TRANSPORT)
|
||||
.setComponent(ENCRYPTING_TRANSPORT)
|
||||
.putExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY, tranportComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link Intent} which resolves to the {@link IBackupTransport} for the {@link
|
||||
* ComponentName}.
|
||||
@@ -75,32 +58,6 @@ public class TransportClientManager {
|
||||
return new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a {@link Intent} originally created by {@link
|
||||
* #getEncryptingTransportIntent(ComponentName)}, returns the {@link Intent} which resolves to
|
||||
* the {@link IBackupTransport} for that {@link ComponentName}.
|
||||
*/
|
||||
public static Intent getRealTransportIntent(Intent encryptingTransportIntent) {
|
||||
ComponentName transportComponent = encryptingTransportIntent.getParcelableExtra(
|
||||
ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
|
||||
Intent intent = getRealTransportIntent(transportComponent)
|
||||
.putExtras(encryptingTransportIntent.getExtras());
|
||||
intent.removeExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
|
||||
return intent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link TransportClientManager} such that {@link #getTransportClient(ComponentName,
|
||||
* Bundle, String)} returns a {@link TransportClient} which connects to an intermediate {@link
|
||||
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
|
||||
* the {@link IBackupTransport} for the given {@link ComponentName}.
|
||||
*/
|
||||
public static TransportClientManager createEncryptingClientManager(@UserIdInt int userId,
|
||||
Context context, TransportStats transportStats) {
|
||||
return new TransportClientManager(userId, context, transportStats,
|
||||
TransportClientManager::getEncryptingTransportIntent);
|
||||
}
|
||||
|
||||
public TransportClientManager(@UserIdInt int userId, Context context,
|
||||
TransportStats transportStats) {
|
||||
this(userId, context, transportStats, TransportClientManager::getRealTransportIntent);
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.transport;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.backup.RestoreDescription;
|
||||
import android.app.backup.RestoreSet;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DelegatingTransportTest {
|
||||
@Mock private IBackupTransport mBackupTransport;
|
||||
@Mock private PackageInfo mPackageInfo;
|
||||
@Mock private ParcelFileDescriptor mFd;
|
||||
|
||||
private final String mPackageName = "testpackage";
|
||||
private final RestoreSet mRestoreSet = new RestoreSet();
|
||||
private final int mFlags = 1;
|
||||
private final long mRestoreToken = 10;
|
||||
private final long mSize = 100;
|
||||
private final int mNumBytes = 1000;
|
||||
private DelegatingTransport mDelegatingTransport;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mDelegatingTransport = new DelegatingTransport() {
|
||||
@Override
|
||||
protected IBackupTransport getDelegate() {
|
||||
return mBackupTransport;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testName() throws RemoteException {
|
||||
String exp = "dummy";
|
||||
when(mBackupTransport.name()).thenReturn(exp);
|
||||
|
||||
String ret = mDelegatingTransport.name();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).name();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigurationIntent() throws RemoteException {
|
||||
Intent exp = new Intent("dummy");
|
||||
when(mBackupTransport.configurationIntent()).thenReturn(exp);
|
||||
|
||||
Intent ret = mDelegatingTransport.configurationIntent();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).configurationIntent();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentDestinationString() throws RemoteException {
|
||||
String exp = "dummy";
|
||||
when(mBackupTransport.currentDestinationString()).thenReturn(exp);
|
||||
|
||||
String ret = mDelegatingTransport.currentDestinationString();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).currentDestinationString();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataManagementIntent() throws RemoteException {
|
||||
Intent exp = new Intent("dummy");
|
||||
when(mBackupTransport.dataManagementIntent()).thenReturn(exp);
|
||||
|
||||
Intent ret = mDelegatingTransport.dataManagementIntent();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).dataManagementIntent();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataManagementIntentLabel() throws RemoteException {
|
||||
String exp = "dummy";
|
||||
when(mBackupTransport.dataManagementIntentLabel()).thenReturn(exp);
|
||||
|
||||
CharSequence ret = mDelegatingTransport.dataManagementIntentLabel();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).dataManagementIntentLabel();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportDirName() throws RemoteException {
|
||||
String exp = "dummy";
|
||||
when(mBackupTransport.transportDirName()).thenReturn(exp);
|
||||
|
||||
String ret = mDelegatingTransport.transportDirName();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).transportDirName();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestBackupTime() throws RemoteException {
|
||||
long exp = 1000L;
|
||||
when(mBackupTransport.requestBackupTime()).thenReturn(exp);
|
||||
|
||||
long ret = mDelegatingTransport.requestBackupTime();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).requestBackupTime();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitializeDevice() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.initializeDevice()).thenReturn(exp);
|
||||
|
||||
long ret = mDelegatingTransport.initializeDevice();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).initializeDevice();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformBackup() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.performBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.performBackup(mPackageInfo, mFd, mFlags);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).performBackup(mPackageInfo, mFd, mFlags);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearBackupData() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.clearBackupData(mPackageInfo)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.clearBackupData(mPackageInfo);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).clearBackupData(mPackageInfo);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFinishBackup() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.finishBackup()).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.finishBackup();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).finishBackup();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAvailableRestoreSets() throws RemoteException {
|
||||
RestoreSet[] exp = new RestoreSet[] {mRestoreSet};
|
||||
when(mBackupTransport.getAvailableRestoreSets()).thenReturn(exp);
|
||||
|
||||
RestoreSet[] ret = mDelegatingTransport.getAvailableRestoreSets();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).getAvailableRestoreSets();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentRestoreSet() throws RemoteException {
|
||||
long exp = 1000;
|
||||
when(mBackupTransport.getCurrentRestoreSet()).thenReturn(exp);
|
||||
|
||||
long ret = mDelegatingTransport.getCurrentRestoreSet();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).getCurrentRestoreSet();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartRestore() throws RemoteException {
|
||||
int exp = 1000;
|
||||
PackageInfo[] packageInfos = {mPackageInfo};
|
||||
when(mBackupTransport.startRestore(mRestoreToken, packageInfos)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.startRestore(mRestoreToken, packageInfos);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).startRestore(mRestoreToken, packageInfos);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextRestorePackage() throws RemoteException {
|
||||
RestoreDescription exp = new RestoreDescription(mPackageName, 1);
|
||||
when(mBackupTransport.nextRestorePackage()).thenReturn(exp);
|
||||
|
||||
RestoreDescription ret = mDelegatingTransport.nextRestorePackage();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).nextRestorePackage();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestoreData() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.getRestoreData(mFd)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.getRestoreData(mFd);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).getRestoreData(mFd);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tesFinishRestore() throws RemoteException {
|
||||
mDelegatingTransport.finishRestore();
|
||||
|
||||
verify(mBackupTransport, times(1)).finishRestore();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestFullBackupTime() throws RemoteException {
|
||||
long exp = 1000L;
|
||||
when(mBackupTransport.requestFullBackupTime()).thenReturn(exp);
|
||||
|
||||
long ret = mDelegatingTransport.requestFullBackupTime();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).requestFullBackupTime();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPerformFullBackup() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.performFullBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.performFullBackup(mPackageInfo, mFd, mFlags);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).performFullBackup(mPackageInfo, mFd, mFlags);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckFullBackupSize() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.checkFullBackupSize(mSize)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.checkFullBackupSize(mSize);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).checkFullBackupSize(mSize);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendBackupData() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.sendBackupData(mNumBytes)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.sendBackupData(mNumBytes);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).sendBackupData(mNumBytes);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancelFullBackup() throws RemoteException {
|
||||
mDelegatingTransport.cancelFullBackup();
|
||||
|
||||
verify(mBackupTransport, times(1)).cancelFullBackup();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAppEligibleForBackup() throws RemoteException {
|
||||
boolean exp = true;
|
||||
when(mBackupTransport.isAppEligibleForBackup(mPackageInfo, true)).thenReturn(exp);
|
||||
|
||||
boolean ret = mDelegatingTransport.isAppEligibleForBackup(mPackageInfo, true);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).isAppEligibleForBackup(mPackageInfo, true);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBackupQuota() throws RemoteException {
|
||||
long exp = 1000;
|
||||
when(mBackupTransport.getBackupQuota(mPackageName, true)).thenReturn(exp);
|
||||
|
||||
long ret = mDelegatingTransport.getBackupQuota(mPackageName, true);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).getBackupQuota(mPackageName, true);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNextFullRestoreDataChunk() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.getNextFullRestoreDataChunk(mFd)).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.getNextFullRestoreDataChunk(mFd);
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).getNextFullRestoreDataChunk(mFd);
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbortFullRestore() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.abortFullRestore()).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.abortFullRestore();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).abortFullRestore();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTransportFlags() throws RemoteException {
|
||||
int exp = 1000;
|
||||
when(mBackupTransport.getTransportFlags()).thenReturn(exp);
|
||||
|
||||
int ret = mDelegatingTransport.getTransportFlags();
|
||||
|
||||
assertEquals(exp, ret);
|
||||
verify(mBackupTransport, times(1)).getTransportFlags();
|
||||
verifyNoMoreInteractions(mBackupTransport);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user