| /* |
| * 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.os.UserHandle; |
| import android.util.Slog; |
| |
| import com.android.internal.util.Preconditions; |
| import com.android.server.AppWidgetBackupBridge; |
| import com.android.server.backup.BackupAgentTimeoutParameters; |
| import com.android.server.backup.BackupRestoreTask; |
| import com.android.server.backup.UserBackupManagerFiles; |
| 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; |
| |
| /** |
| * 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; |
| OutputStream mOutput; |
| FullBackupPreflight mPreflightHook; |
| BackupRestoreTask mTimeoutMonitor; |
| IBackupAgent mAgent; |
| boolean mIncludeApks; |
| 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( |
| PackageInfo packageInfo, |
| IBackupAgent agent, |
| ParcelFileDescriptor pipe, |
| int token, |
| boolean includeApks) |
| throws IOException { |
| // TODO: http://b/22388012 |
| mUserId = UserHandle.USER_SYSTEM; |
| mPackageManager = backupManagerService.getPackageManager(); |
| mPackage = packageInfo; |
| mAgent = agent; |
| mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor()); |
| mToken = token; |
| mIncludeApks = includeApks; |
| mFilesDir = UserBackupManagerFiles.getFullBackupEngineFilesDir(mUserId); |
| } |
| |
| @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(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); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Remote agent vanished during full backup of " + mPackage.packageName); |
| } 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 = |
| Preconditions.checkNotNull( |
| 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(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); |
| } |
| } |
| } |