blob: 846c6a23d39426a4ad8ff74c5c28937a26d0fb2e [file] [log] [blame]
/*
* Copyright (C) 2017 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.fullbackup;
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import android.annotation.UserIdInt;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackupDataOutput;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.utils.FullBackupUtils;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
/**
* Core logic for performing one package's full backup, gathering the tarball from the application
* and emitting it to the designated OutputStream.
*/
public class FullBackupEngine {
private UserBackupManagerService backupManagerService;
private OutputStream mOutput;
private FullBackupPreflight mPreflightHook;
private BackupRestoreTask mTimeoutMonitor;
private IBackupAgent mAgent;
private boolean mIncludeApks;
private PackageInfo mPkg;
private final long mQuota;
private final int mOpToken;
private final int mTransportFlags;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
class FullBackupRunner implements Runnable {
private final @UserIdInt int mUserId;
private final PackageManager mPackageManager;
private final PackageInfo mPackage;
private final IBackupAgent mAgent;
private final ParcelFileDescriptor mPipe;
private final int mToken;
private final boolean mIncludeApks;
private final File mFilesDir;
FullBackupRunner(
UserBackupManagerService userBackupManagerService,
PackageInfo packageInfo,
IBackupAgent agent,
ParcelFileDescriptor pipe,
int token,
boolean includeApks)
throws IOException {
mUserId = userBackupManagerService.getUserId();
mPackageManager = backupManagerService.getPackageManager();
mPackage = packageInfo;
mAgent = agent;
mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
mToken = token;
mIncludeApks = includeApks;
mFilesDir = userBackupManagerService.getDataDir();
}
@Override
public void run() {
try {
FullBackupDataOutput output =
new FullBackupDataOutput(mPipe, /* quota */ -1, mTransportFlags);
AppMetadataBackupWriter appMetadataBackupWriter =
new AppMetadataBackupWriter(output, mPackageManager);
String packageName = mPackage.packageName;
boolean isSharedStorage = SHARED_BACKUP_AGENT_PACKAGE.equals(packageName);
boolean writeApk =
shouldWriteApk(mPackage.applicationInfo, mIncludeApks, isSharedStorage);
if (!isSharedStorage) {
if (MORE_DEBUG) {
Slog.d(TAG, "Writing manifest for " + packageName);
}
File manifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME);
appMetadataBackupWriter.backupManifest(
mPackage, manifestFile, mFilesDir, writeApk);
manifestFile.delete();
// Write widget data.
byte[] widgetData =
AppWidgetBackupBridge.getWidgetState(packageName, mUserId);
if (widgetData != null && widgetData.length > 0) {
File metadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
appMetadataBackupWriter.backupWidget(
mPackage, metadataFile, mFilesDir, widgetData);
metadataFile.delete();
}
}
// TODO(b/113807190): Look into removing, only used for 'adb backup'.
if (writeApk) {
appMetadataBackupWriter.backupApk(mPackage);
appMetadataBackupWriter.backupObb(mUserId, mPackage);
}
if (DEBUG) {
Slog.d(TAG, "Calling doFullBackup() on " + packageName);
}
long timeout =
isSharedStorage
? mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis()
: mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
backupManagerService.prepareOperationTimeout(
mToken,
timeout,
mTimeoutMonitor /* in parent class */,
OP_TYPE_BACKUP_WAIT);
mAgent.doFullBackup(
mPipe,
mQuota,
mToken,
backupManagerService.getBackupManagerBinder(),
mTransportFlags);
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName, e);
} catch (RemoteException e) {
Slog.e(
TAG,
"Remote agent vanished during full backup of " + mPackage.packageName,
e);
} finally {
try {
mPipe.close();
} catch (IOException e) {
}
}
}
/**
* Don't write apks for system-bundled apps that are not upgraded.
*/
private boolean shouldWriteApk(
ApplicationInfo applicationInfo, boolean includeApks, boolean isSharedStorage) {
boolean isSystemApp = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
boolean isUpdatedSystemApp =
(applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
return includeApks
&& !isSharedStorage
&& (!isSystemApp || isUpdatedSystemApp);
}
}
public FullBackupEngine(
UserBackupManagerService backupManagerService,
OutputStream output,
FullBackupPreflight preflightHook,
PackageInfo pkg,
boolean alsoApks,
BackupRestoreTask timeoutMonitor,
long quota,
int opToken,
int transportFlags) {
this.backupManagerService = backupManagerService;
mOutput = output;
mPreflightHook = preflightHook;
mPkg = pkg;
mIncludeApks = alsoApks;
mTimeoutMonitor = timeoutMonitor;
mQuota = quota;
mOpToken = opToken;
mTransportFlags = transportFlags;
mAgentTimeoutParameters =
Objects.requireNonNull(
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
}
public int preflightCheck() throws RemoteException {
if (mPreflightHook == null) {
if (MORE_DEBUG) {
Slog.v(TAG, "No preflight check");
}
return BackupTransport.TRANSPORT_OK;
}
if (initializeAgent()) {
int result = mPreflightHook.preflightFullBackup(mPkg, mAgent);
if (MORE_DEBUG) {
Slog.v(TAG, "preflight returned " + result);
}
return result;
} else {
Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName);
return BackupTransport.AGENT_ERROR;
}
}
public int backupOnePackage() throws RemoteException {
int result = BackupTransport.AGENT_ERROR;
if (initializeAgent()) {
ParcelFileDescriptor[] pipes = null;
try {
pipes = ParcelFileDescriptor.createPipe();
FullBackupRunner runner =
new FullBackupRunner(
backupManagerService,
mPkg,
mAgent,
pipes[1],
mOpToken,
mIncludeApks);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner, "app-data-runner");
t.start();
FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
if (!backupManagerService.waitUntilOperationComplete(mOpToken)) {
Slog.e(TAG, "Full backup failed on package " + mPkg.packageName);
} else {
if (MORE_DEBUG) {
Slog.d(TAG, "Full package backup success: " + mPkg.packageName);
}
result = BackupTransport.TRANSPORT_OK;
}
} catch (IOException e) {
Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage());
result = BackupTransport.AGENT_ERROR;
} finally {
try {
// flush after every package
mOutput.flush();
if (pipes != null) {
if (pipes[0] != null) {
pipes[0].close();
}
if (pipes[1] != null) {
pipes[1].close();
}
}
} catch (IOException e) {
Slog.w(TAG, "Error bringing down backup stack");
result = BackupTransport.TRANSPORT_ERROR;
}
}
} else {
Slog.w(TAG, "Unable to bind to full agent for " + mPkg.packageName);
}
tearDown();
return result;
}
public void sendQuotaExceeded(long backupDataBytes, long quotaBytes) {
if (initializeAgent()) {
try {
RemoteCall.execute(
callback -> mAgent.doQuotaExceeded(backupDataBytes, quotaBytes, callback),
mAgentTimeoutParameters.getQuotaExceededTimeoutMillis());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception while telling agent about quota exceeded");
}
}
}
private boolean initializeAgent() {
if (mAgent == null) {
if (MORE_DEBUG) {
Slog.d(TAG, "Binding to full backup agent : " + mPkg.packageName);
}
mAgent =
backupManagerService.bindToAgentSynchronous(
mPkg.applicationInfo, ApplicationThreadConstants.BACKUP_MODE_FULL);
}
return mAgent != null;
}
private void tearDown() {
if (mPkg != null) {
backupManagerService.tearDownAgentAndKill(mPkg.applicationInfo);
}
}
}