Route EncryptedLocalTransport KV backup/restore through encryption code
Bug: 142227548
Test: Verify the device boots successfully
Verify EncryptedLocalTransport APK is present
Verify manual backup/restore using bmgr for LocalTransport and EncryptedLocalTransport
For LocalTransport (unencrypted) and EncryptedLocalTransport:
atest CtsBackupTestCases
atest CtsBackupHostTestCases
atest GtsBackupTestCases
atest GtsBackupHostTestCases
Change-Id: Iac3a8a50d7f761442a4b784cfba3a980e900dd7f
This commit is contained in:
committed by
Al Sutton
parent
d0844929a3
commit
004e85f798
@@ -29,6 +29,8 @@
|
||||
'service' attribute here is a flattened ComponentName string. -->
|
||||
<backup-transport-whitelisted-service
|
||||
service="com.android.localtransport/.LocalTransportService" />
|
||||
<backup-transport-whitelisted-service
|
||||
service="com.android.encryptedlocaltransport/.EncryptedLocalTransportService" />
|
||||
|
||||
<!-- Whitelist Shell to use the bugreport API -->
|
||||
<bugreport-whitelisted package="com.android.shell" />
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
android_app {
|
||||
name: "BackupEncryption",
|
||||
srcs: ["src/**/*.java"],
|
||||
libs: ["backup-encryption-protos"],
|
||||
static_libs: ["backuplib"],
|
||||
static_libs: ["backup-encryption-protos", "backuplib"],
|
||||
optimize: { enabled: false },
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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.content.Context;
|
||||
import android.security.keystore.recovery.InternalRecoveryServiceException;
|
||||
import android.security.keystore.recovery.RecoveryController;
|
||||
|
||||
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
|
||||
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
|
||||
import com.android.server.backup.encryption.keys.TertiaryKeyManager;
|
||||
import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
class EncryptionKeyHelper {
|
||||
private static SecureRandom sSecureRandom = new SecureRandom();
|
||||
|
||||
private final Context mContext;
|
||||
private final RecoverableKeyStoreSecondaryKeyManager
|
||||
.RecoverableKeyStoreSecondaryKeyManagerProvider
|
||||
mSecondaryKeyManagerProvider;
|
||||
|
||||
EncryptionKeyHelper(Context context) {
|
||||
mContext = context;
|
||||
mSecondaryKeyManagerProvider =
|
||||
() ->
|
||||
new RecoverableKeyStoreSecondaryKeyManager(
|
||||
RecoveryController.getInstance(mContext), sSecureRandom);
|
||||
}
|
||||
|
||||
RecoverableKeyStoreSecondaryKeyManager
|
||||
.RecoverableKeyStoreSecondaryKeyManagerProvider getKeyManagerProvider() {
|
||||
return mSecondaryKeyManagerProvider;
|
||||
}
|
||||
|
||||
RecoverableKeyStoreSecondaryKey getActiveSecondaryKey()
|
||||
throws UnrecoverableKeyException, InternalRecoveryServiceException {
|
||||
String keyAlias = CryptoSettings.getInstance(mContext).getActiveSecondaryKeyAlias().get();
|
||||
return mSecondaryKeyManagerProvider.get().get(keyAlias).get();
|
||||
}
|
||||
|
||||
SecretKey getTertiaryKey(
|
||||
String packageName,
|
||||
RecoverableKeyStoreSecondaryKey secondaryKey)
|
||||
throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
|
||||
NoSuchAlgorithmException, IOException, NoSuchPaddingException,
|
||||
InvalidKeyException {
|
||||
TertiaryKeyManager tertiaryKeyManager =
|
||||
new TertiaryKeyManager(
|
||||
mContext,
|
||||
sSecureRandom,
|
||||
TertiaryKeyRotationScheduler.getInstance(mContext),
|
||||
secondaryKey,
|
||||
packageName);
|
||||
return tertiaryKeyManager.getKey();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 static com.android.server.backup.encryption.BackupEncryptionService.TAG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.backup.encryption.client.CryptoBackupServer;
|
||||
import com.android.server.backup.encryption.keys.KeyWrapUtils;
|
||||
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
|
||||
import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
|
||||
import com.android.server.backup.encryption.tasks.EncryptedKvBackupTask;
|
||||
import com.android.server.backup.encryption.tasks.EncryptedKvRestoreTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
|
||||
public class KeyValueEncrypter {
|
||||
private final Context mContext;
|
||||
private final EncryptionKeyHelper mKeyHelper;
|
||||
|
||||
public KeyValueEncrypter(Context context) {
|
||||
mContext = context;
|
||||
mKeyHelper = new EncryptionKeyHelper(mContext);
|
||||
}
|
||||
|
||||
public void encryptKeyValueData(
|
||||
String packageName, ParcelFileDescriptor inputFd, OutputStream outputStream)
|
||||
throws Exception {
|
||||
EncryptedKvBackupTask.EncryptedKvBackupTaskFactory backupTaskFactory =
|
||||
new EncryptedKvBackupTask.EncryptedKvBackupTaskFactory();
|
||||
EncryptedKvBackupTask backupTask =
|
||||
backupTaskFactory.newInstance(
|
||||
mContext,
|
||||
new SecureRandom(),
|
||||
new FileBackupServer(outputStream),
|
||||
CryptoSettings.getInstance(mContext),
|
||||
mKeyHelper.getKeyManagerProvider(),
|
||||
inputFd,
|
||||
packageName);
|
||||
backupTask.performBackup(/* incremental */ false);
|
||||
}
|
||||
|
||||
public void decryptKeyValueData(String packageName,
|
||||
InputStream encryptedInputStream, ParcelFileDescriptor outputFd) throws Exception {
|
||||
RecoverableKeyStoreSecondaryKey secondaryKey = mKeyHelper.getActiveSecondaryKey();
|
||||
|
||||
EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory restoreTaskFactory =
|
||||
new EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory();
|
||||
EncryptedKvRestoreTask restoreTask =
|
||||
restoreTaskFactory.newInstance(
|
||||
mContext,
|
||||
mKeyHelper.getKeyManagerProvider(),
|
||||
new InputStreamFullRestoreDownloader(encryptedInputStream),
|
||||
secondaryKey.getAlias(),
|
||||
KeyWrapUtils.wrap(
|
||||
secondaryKey.getSecretKey(),
|
||||
mKeyHelper.getTertiaryKey(packageName, secondaryKey)));
|
||||
|
||||
restoreTask.getRestoreData(outputFd);
|
||||
}
|
||||
|
||||
// TODO(b/142455725): Extract into a commong class.
|
||||
private static class FileBackupServer implements CryptoBackupServer {
|
||||
private static final String EMPTY_DOC_ID = "";
|
||||
|
||||
private final OutputStream mOutputStream;
|
||||
|
||||
FileBackupServer(OutputStream outputStream) {
|
||||
mOutputStream = outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadIncrementalBackup(
|
||||
String packageName,
|
||||
String oldDocId,
|
||||
byte[] diffScript,
|
||||
WrappedKeyProto.WrappedKey tertiaryKey) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String uploadNonIncrementalBackup(
|
||||
String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
|
||||
try {
|
||||
mOutputStream.write(data);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to write encrypted data to file: ", e);
|
||||
}
|
||||
|
||||
return EMPTY_DOC_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActiveSecondaryKeyAlias(
|
||||
String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/142455725): Extract into a commong class.
|
||||
private static class InputStreamFullRestoreDownloader extends FullRestoreDownloader {
|
||||
private final InputStream mInputStream;
|
||||
|
||||
InputStreamFullRestoreDownloader(InputStream inputStream) {
|
||||
mInputStream = inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readNextChunk(byte[] buffer) throws IOException {
|
||||
return mInputStream.read(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish(FinishType finishType) {
|
||||
try {
|
||||
mInputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Error while reading restore data");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,27 +18,58 @@ 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) {
|
||||
IntermediateEncryptingTransport(
|
||||
TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
|
||||
boolean shouldEncrypt) {
|
||||
mTransportClient = transportClient;
|
||||
mContext = context;
|
||||
mKeyValueEncrypter = keyValueEncrypter;
|
||||
mShouldEncrypt = shouldEncrypt;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,9 +77,116 @@ public class IntermediateEncryptingTransport extends DelegatingTransport {
|
||||
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) {
|
||||
@@ -65,4 +203,9 @@ public class IntermediateEncryptingTransport extends DelegatingTransport {
|
||||
TransportClient getClient() {
|
||||
return mTransportClient;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setNextRestorePackage(String nextRestorePackage) {
|
||||
mNextRestorePackage.set(nextRestorePackage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,20 +26,20 @@ 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.
|
||||
*/
|
||||
/** 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) {
|
||||
@@ -48,6 +48,7 @@ public class IntermediateEncryptingTransportManager {
|
||||
|
||||
public IntermediateEncryptingTransportManager(Context context) {
|
||||
this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,31 +56,42 @@ public class IntermediateEncryptingTransportManager {
|
||||
* 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)}.
|
||||
* 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));
|
||||
return mTransports.computeIfAbsent(
|
||||
transportIntent.getComponent(), c -> create(transportIntent));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link IntermediateEncryptingTransport}.
|
||||
*/
|
||||
/** Create an instance of {@link IntermediateEncryptingTransport}. */
|
||||
private IntermediateEncryptingTransport create(Intent realTransportIntent) {
|
||||
Log.d(TAG, "create: intent:" + realTransportIntent);
|
||||
return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
|
||||
realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
|
||||
|
||||
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}.
|
||||
* 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);
|
||||
|
||||
@@ -18,43 +18,71 @@ 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 {
|
||||
@Mock private IBackupTransport mRealTransport;
|
||||
@Mock private TransportClient mTransportClient;
|
||||
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);
|
||||
|
||||
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 {
|
||||
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
|
||||
|
||||
IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
|
||||
|
||||
assertEquals(mRealTransport, ret);
|
||||
@@ -74,4 +102,79 @@ public class IntermediateEncryptingTransportTest {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
packages/EncryptedLocalTransport/Android.bp
Normal file
27
packages/EncryptedLocalTransport/Android.bp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
android_app {
|
||||
name: "EncryptedLocalTransport",
|
||||
srcs: ["src/**/*.java"],
|
||||
optimize: {
|
||||
proguard_flags_files: ["proguard.flags"],
|
||||
},
|
||||
static_libs: ["LocalTransport"],
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
privileged: true,
|
||||
}
|
||||
36
packages/EncryptedLocalTransport/AndroidManifest.xml
Normal file
36
packages/EncryptedLocalTransport/AndroidManifest.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Copyright (c) 2019 Google Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.encryptedlocaltransport"
|
||||
android:sharedUserId="android.uid.system" >
|
||||
|
||||
|
||||
<application android:allowBackup="false" >
|
||||
<!-- This service does not need to be exported because it shares uid with the system server
|
||||
which is the only client. -->
|
||||
<service android:name=".EncryptedLocalTransportService"
|
||||
android:permission="android.permission.CONFIRM_FULL_BACKUP"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.backup.TRANSPORT_HOST" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
2
packages/EncryptedLocalTransport/proguard.flags
Normal file
2
packages/EncryptedLocalTransport/proguard.flags
Normal file
@@ -0,0 +1,2 @@
|
||||
-keep class com.android.localTransport.EncryptedLocalTransport
|
||||
-keep class com.android.localTransport.EncryptedLocalTransportService
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.encryptedlocaltransport;
|
||||
|
||||
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.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.StructStat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.localtransport.LocalTransport;
|
||||
import com.android.localtransport.LocalTransportParameters;
|
||||
|
||||
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.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class EncryptedLocalTransport extends LocalTransport {
|
||||
private static final String TAG = "EncryptedLocalTransport";
|
||||
private static final int BACKUP_BUFFER_SIZE = 32 * 1024; // 32 KB.
|
||||
|
||||
public EncryptedLocalTransport(Context context,
|
||||
LocalTransportParameters parameters) {
|
||||
super(context, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int performBackup(
|
||||
PackageInfo packageInfo, ParcelFileDescriptor data, int flags) {
|
||||
File packageFile;
|
||||
try {
|
||||
StructStat stat = Os.fstat(data.getFileDescriptor());
|
||||
if (stat.st_size > KEY_VALUE_BACKUP_SIZE_QUOTA) {
|
||||
Log.w(TAG, "New datastore size " + stat.st_size
|
||||
+ " exceeds quota " + KEY_VALUE_BACKUP_SIZE_QUOTA);
|
||||
return TRANSPORT_QUOTA_EXCEEDED;
|
||||
}
|
||||
} catch (ErrnoException e) {
|
||||
Log.w(TAG, "Failed to stat the backup input file: ", e);
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
clearBackupData(packageInfo);
|
||||
|
||||
try (InputStream in = new FileInputStream(data.getFileDescriptor())) {
|
||||
packageFile = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
|
||||
Files.copy(in, packageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to save backup data to file: ", e);
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
return TRANSPORT_OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRestoreData(ParcelFileDescriptor outFd) {
|
||||
if (mRestorePackages == null) {
|
||||
throw new IllegalStateException("startRestore not called");
|
||||
}
|
||||
if (mRestorePackage < 0) {
|
||||
throw new IllegalStateException("nextRestorePackage not called");
|
||||
}
|
||||
if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
|
||||
throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
|
||||
}
|
||||
|
||||
try(OutputStream out = new FileOutputStream(outFd.getFileDescriptor())) {
|
||||
File packageFile = new File(mRestoreSetIncrementalDir,
|
||||
mRestorePackages[mRestorePackage].packageName);
|
||||
Files.copy(packageFile.toPath(), out);
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Failed to transfer restore data: " + e);
|
||||
return BackupTransport.TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
return BackupTransport.TRANSPORT_OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasRestoreDataForPackage(String packageName) {
|
||||
File contents = (new File(mRestoreSetIncrementalDir, packageName));
|
||||
return contents.exists() && contents.length() != 0;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.encryptedlocaltransport;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import com.android.localtransport.LocalTransportParameters;
|
||||
|
||||
public class EncryptedLocalTransportService extends Service {
|
||||
private static EncryptedLocalTransport sTransport = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
if (sTransport == null) {
|
||||
LocalTransportParameters parameters =
|
||||
new LocalTransportParameters(getMainThreadHandler(), getContentResolver());
|
||||
sTransport = new EncryptedLocalTransport(this, parameters);
|
||||
}
|
||||
sTransport.getParameters().start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
sTransport.getParameters().stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return sTransport.getBinder();
|
||||
}
|
||||
}
|
||||
@@ -71,19 +71,19 @@ public class LocalTransport extends BackupTransport {
|
||||
|
||||
// Size quotas at reasonable values, similar to the current cloud-storage limits
|
||||
private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
|
||||
private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
|
||||
protected static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
|
||||
|
||||
private Context mContext;
|
||||
private File mDataDir;
|
||||
private File mCurrentSetDir;
|
||||
private File mCurrentSetIncrementalDir;
|
||||
protected File mCurrentSetIncrementalDir;
|
||||
private File mCurrentSetFullDir;
|
||||
|
||||
private PackageInfo[] mRestorePackages = null;
|
||||
private int mRestorePackage = -1; // Index into mRestorePackages
|
||||
private int mRestoreType;
|
||||
protected PackageInfo[] mRestorePackages = null;
|
||||
protected int mRestorePackage = -1; // Index into mRestorePackages
|
||||
protected int mRestoreType;
|
||||
private File mRestoreSetDir;
|
||||
private File mRestoreSetIncrementalDir;
|
||||
protected File mRestoreSetIncrementalDir;
|
||||
private File mRestoreSetFullDir;
|
||||
|
||||
// Additional bookkeeping for full backup
|
||||
@@ -115,7 +115,7 @@ public class LocalTransport extends BackupTransport {
|
||||
makeDataDirs();
|
||||
}
|
||||
|
||||
LocalTransportParameters getParameters() {
|
||||
public LocalTransportParameters getParameters() {
|
||||
return mParameters;
|
||||
}
|
||||
|
||||
@@ -537,14 +537,14 @@ public class LocalTransport extends BackupTransport {
|
||||
int bytesLeft = numBytes;
|
||||
while (bytesLeft > 0) {
|
||||
try {
|
||||
int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
|
||||
if (nRead < 0) {
|
||||
// Something went wrong if we expect data but saw EOD
|
||||
Log.w(TAG, "Unexpected EOD; failing backup");
|
||||
return TRANSPORT_ERROR;
|
||||
}
|
||||
mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
|
||||
bytesLeft -= nRead;
|
||||
int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
|
||||
if (nRead < 0) {
|
||||
// Something went wrong if we expect data but saw EOD
|
||||
Log.w(TAG, "Unexpected EOD; failing backup");
|
||||
return TRANSPORT_ERROR;
|
||||
}
|
||||
mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
|
||||
bytesLeft -= nRead;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
|
||||
return TRANSPORT_ERROR;
|
||||
@@ -620,20 +620,15 @@ public class LocalTransport extends BackupTransport {
|
||||
}
|
||||
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
|
||||
|
||||
boolean found = false;
|
||||
boolean found;
|
||||
while (++mRestorePackage < mRestorePackages.length) {
|
||||
String name = mRestorePackages[mRestorePackage].packageName;
|
||||
|
||||
// If we have key/value data for this package, deliver that
|
||||
// skip packages where we have a data dir but no actual contents
|
||||
String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
|
||||
if (contents != null && contents.length > 0) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
|
||||
+ mRestorePackage + " = " + name);
|
||||
}
|
||||
found = hasRestoreDataForPackage(name);
|
||||
if (found) {
|
||||
mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
@@ -664,6 +659,18 @@ public class LocalTransport extends BackupTransport {
|
||||
return RestoreDescription.NO_MORE_PACKAGES;
|
||||
}
|
||||
|
||||
protected boolean hasRestoreDataForPackage(String packageName) {
|
||||
String[] contents = (new File(mRestoreSetIncrementalDir, packageName)).list();
|
||||
if (contents != null && contents.length > 0) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
|
||||
+ mRestorePackage + " = " + packageName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRestoreData(ParcelFileDescriptor outFd) {
|
||||
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.util.KeyValueListParser;
|
||||
|
||||
class LocalTransportParameters extends KeyValueSettingObserver {
|
||||
public class LocalTransportParameters extends KeyValueSettingObserver {
|
||||
private static final String TAG = "LocalTransportParams";
|
||||
private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
|
||||
private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
|
||||
@@ -31,7 +31,7 @@ class LocalTransportParameters extends KeyValueSettingObserver {
|
||||
private boolean mFakeEncryptionFlag;
|
||||
private boolean mIsNonIncrementalOnly;
|
||||
|
||||
LocalTransportParameters(Handler handler, ContentResolver resolver) {
|
||||
public LocalTransportParameters(Handler handler, ContentResolver resolver) {
|
||||
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +239,6 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
|
||||
private final KeyValueBackupReporter mReporter;
|
||||
private final OnTaskFinishedListener mTaskFinishedListener;
|
||||
private final boolean mUserInitiated;
|
||||
private final boolean mNonIncremental;
|
||||
private final int mCurrentOpToken;
|
||||
private final int mUserId;
|
||||
private final File mStateDirectory;
|
||||
@@ -264,6 +263,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
|
||||
// and at least one of the packages had data. Used to avoid updating current token for
|
||||
// empty backups.
|
||||
private boolean mHasDataToBackup;
|
||||
private boolean mNonIncremental;
|
||||
|
||||
/**
|
||||
* This {@link ConditionVariable} is used to signal that the cancel operation has been
|
||||
@@ -412,6 +412,11 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
|
||||
try {
|
||||
IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.startTask()");
|
||||
String transportName = transport.name();
|
||||
if (transportName.contains("EncryptedLocalTransport")) {
|
||||
// Temporary code for EiTF POC. Only supports non-incremental backups.
|
||||
mNonIncremental = true;
|
||||
}
|
||||
|
||||
mReporter.onTransportReady(transportName);
|
||||
|
||||
// If we haven't stored PM metadata yet, we must initialize the transport.
|
||||
|
||||
Reference in New Issue
Block a user