blob: d0a88813fa5ecac48ff1893491131f13a135117e [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.restore;
import static android.app.backup.BackupManager.OperationType;
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_SESSION_TIMEOUT;
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_GET_RESTORE_SETS;
import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.backup.BackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
import android.app.backup.RestoreSet;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.util.Slog;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.params.RestoreGetSetsParams;
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.utils.BackupEligibilityRules;
import java.util.function.BiFunction;
/**
* Restore session.
*/
public class ActiveRestoreSession extends IRestoreSession.Stub {
private static final String TAG = "RestoreSession";
private static final String DEVICE_NAME_FOR_D2D_SET = "D2D";
private final TransportManager mTransportManager;
private final String mTransportName;
private final UserBackupManagerService mBackupManagerService;
private final int mUserId;
private final BackupEligibilityRules mBackupEligibilityRules;
@Nullable private final String mPackageName;
public RestoreSet[] mRestoreSets = null;
boolean mEnded = false;
boolean mTimedOut = false;
public ActiveRestoreSession(
UserBackupManagerService backupManagerService,
@Nullable String packageName,
String transportName,
BackupEligibilityRules backupEligibilityRules) {
mBackupManagerService = backupManagerService;
mPackageName = packageName;
mTransportManager = backupManagerService.getTransportManager();
mTransportName = transportName;
mUserId = backupManagerService.getUserId();
mBackupEligibilityRules = backupEligibilityRules;
}
public void markTimedOut() {
mTimedOut = true;
}
// --- Binder interface ---
public synchronized int getAvailableRestoreSets(IRestoreObserver observer,
IBackupManagerMonitor monitor) {
mBackupManagerService.getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP,
"getAvailableRestoreSets");
if (observer == null) {
throw new IllegalArgumentException("Observer must not be null");
}
if (mEnded) {
throw new IllegalStateException("Restore session already ended");
}
if (mTimedOut) {
Slog.i(TAG, "Session already timed out");
return -1;
}
final long oldId = Binder.clearCallingIdentity();
try {
TransportClient transportClient =
mTransportManager.getTransportClient(
mTransportName, "RestoreSession.getAvailableRestoreSets()");
if (transportClient == null) {
Slog.w(TAG, "Null transport client getting restore sets");
return -1;
}
// We know we're doing legit work now, so halt the timeout
// until we're done. It gets started again when the result
// comes in.
mBackupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
UserBackupManagerService.BackupWakeLock wakelock = mBackupManagerService.getWakelock();
wakelock.acquire();
// Prevent lambda from leaking 'this'
TransportManager transportManager = mTransportManager;
OnTaskFinishedListener listener = caller -> {
transportManager.disposeOfTransportClient(transportClient, caller);
wakelock.release();
};
Message msg = mBackupManagerService.getBackupHandler().obtainMessage(
MSG_RUN_GET_RESTORE_SETS,
new RestoreGetSetsParams(transportClient, this, observer, monitor, listener));
mBackupManagerService.getBackupHandler().sendMessage(msg);
return 0;
} catch (Exception e) {
Slog.e(TAG, "Error in getAvailableRestoreSets", e);
return -1;
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
public synchronized int restoreAll(long token, IRestoreObserver observer,
IBackupManagerMonitor monitor) {
mBackupManagerService.getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP,
"performRestore");
if (DEBUG) {
Slog.d(TAG, "restoreAll token=" + Long.toHexString(token)
+ " observer=" + observer);
}
if (mEnded) {
throw new IllegalStateException("Restore session already ended");
}
if (mTimedOut) {
Slog.i(TAG, "Session already timed out");
return -1;
}
if (mRestoreSets == null) {
Slog.e(TAG, "Ignoring restoreAll() with no restore set");
return -1;
}
if (mPackageName != null) {
Slog.e(TAG, "Ignoring restoreAll() on single-package session");
return -1;
}
if (!mTransportManager.isTransportRegistered(mTransportName)) {
Slog.e(TAG, "Transport " + mTransportName + " not registered");
return -1;
}
synchronized (mBackupManagerService.getQueueLock()) {
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
final long oldId = Binder.clearCallingIdentity();
RestoreSet restoreSet = mRestoreSets[i];
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
RestoreParams.createForRestoreAll(
transportClient,
observer,
monitor,
token,
listener,
getBackupEligibilityRules(restoreSet)),
"RestoreSession.restoreAll()");
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
}
}
Slog.w(TAG, "Restore token " + Long.toHexString(token) + " not found");
return -1;
}
// Restores of more than a single package are treated as 'system' restores
public synchronized int restorePackages(long token, @Nullable IRestoreObserver observer,
@NonNull String[] packages, @Nullable IBackupManagerMonitor monitor) {
mBackupManagerService.getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP,
"performRestore");
if (DEBUG) {
StringBuilder b = new StringBuilder(128);
b.append("restorePackages token=");
b.append(Long.toHexString(token));
b.append(" observer=");
if (observer == null) {
b.append("null");
} else {
b.append(observer.toString());
}
b.append(" monitor=");
if (monitor == null) {
b.append("null");
} else {
b.append(monitor.toString());
}
b.append(" packages=");
if (packages == null) {
b.append("null");
} else {
b.append('{');
boolean first = true;
for (String s : packages) {
if (!first) {
b.append(", ");
} else {
first = false;
}
b.append(s);
}
b.append('}');
}
Slog.d(TAG, b.toString());
}
if (mEnded) {
throw new IllegalStateException("Restore session already ended");
}
if (mTimedOut) {
Slog.i(TAG, "Session already timed out");
return -1;
}
if (mRestoreSets == null) {
Slog.e(TAG, "Ignoring restoreAll() with no restore set");
return -1;
}
if (mPackageName != null) {
Slog.e(TAG, "Ignoring restoreAll() on single-package session");
return -1;
}
if (!mTransportManager.isTransportRegistered(mTransportName)) {
Slog.e(TAG, "Transport " + mTransportName + " not registered");
return -1;
}
synchronized (mBackupManagerService.getQueueLock()) {
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
final long oldId = Binder.clearCallingIdentity();
RestoreSet restoreSet = mRestoreSets[i];
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
RestoreParams.createForRestorePackages(
transportClient,
observer,
monitor,
token,
packages,
/* isSystemRestore */ packages.length > 1,
listener,
getBackupEligibilityRules(restoreSet)),
"RestoreSession.restorePackages(" + packages.length + " packages)");
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
}
}
Slog.w(TAG, "Restore token " + Long.toHexString(token) + " not found");
return -1;
}
private BackupEligibilityRules getBackupEligibilityRules(RestoreSet restoreSet) {
// TODO(b/182986784): Remove device name comparison once a designated field for operation
// type is added to RestoreSet object.
int operationType = DEVICE_NAME_FOR_D2D_SET.equals(restoreSet.device)
? OperationType.MIGRATION : OperationType.BACKUP;
return mBackupManagerService.getEligibilityRulesForOperation(operationType);
}
public synchronized int restorePackage(String packageName, IRestoreObserver observer,
IBackupManagerMonitor monitor) {
if (DEBUG) {
Slog.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer
+ "monitor=" + monitor);
}
if (mEnded) {
throw new IllegalStateException("Restore session already ended");
}
if (mTimedOut) {
Slog.i(TAG, "Session already timed out");
return -1;
}
if (mPackageName != null) {
if (!mPackageName.equals(packageName)) {
Slog.e(TAG, "Ignoring attempt to restore pkg=" + packageName
+ " on session for package " + mPackageName);
return -1;
}
}
final PackageInfo app;
try {
app = mBackupManagerService.getPackageManager().getPackageInfoAsUser(
packageName, 0, mUserId);
} catch (NameNotFoundException nnf) {
Slog.w(TAG, "Asked to restore nonexistent pkg " + packageName);
return -1;
}
// If the caller is not privileged and is not coming from the target
// app's uid, throw a permission exception back to the caller.
int perm = mBackupManagerService.getContext().checkPermission(
android.Manifest.permission.BACKUP,
Binder.getCallingPid(), Binder.getCallingUid());
if ((perm == PackageManager.PERMISSION_DENIED) &&
(app.applicationInfo.uid != Binder.getCallingUid())) {
Slog.w(TAG, "restorePackage: bad packageName=" + packageName
+ " or calling uid=" + Binder.getCallingUid());
throw new SecurityException("No permission to restore other packages");
}
if (!mTransportManager.isTransportRegistered(mTransportName)) {
Slog.e(TAG, "Transport " + mTransportName + " not registered");
return -1;
}
// So far so good; we're allowed to try to restore this package.
final long oldId = Binder.clearCallingIdentity();
try {
// Check whether there is data for it in the current dataset, falling back
// to the ancestral dataset if not.
long token = mBackupManagerService.getAvailableRestoreToken(packageName);
if (DEBUG) {
Slog.v(TAG, "restorePackage pkg=" + packageName
+ " token=" + Long.toHexString(token));
}
// If we didn't come up with a place to look -- no ancestral dataset and
// the app has never been backed up from this device -- there's nothing
// to do but return failure.
if (token == 0) {
if (DEBUG) {
Slog.w(TAG, "No data available for this package; not restoring");
}
return -1;
}
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
RestoreParams.createForSinglePackage(
transportClient,
observer,
monitor,
token,
app,
listener,
mBackupEligibilityRules),
"RestoreSession.restorePackage(" + packageName + ")");
} finally {
Binder.restoreCallingIdentity(oldId);
}
}
public void setRestoreSets(RestoreSet[] restoreSets) {
mRestoreSets = restoreSets;
}
/**
* Returns 0 if operation sent or -1 otherwise.
*/
private int sendRestoreToHandlerLocked(
BiFunction<TransportClient, OnTaskFinishedListener, RestoreParams> restoreParamsBuilder,
String callerLogString) {
TransportClient transportClient =
mTransportManager.getTransportClient(mTransportName, callerLogString);
if (transportClient == null) {
Slog.e(TAG, "Transport " + mTransportName + " got unregistered");
return -1;
}
// Stop the session timeout until we finalize the restore
Handler backupHandler = mBackupManagerService.getBackupHandler();
backupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
UserBackupManagerService.BackupWakeLock wakelock = mBackupManagerService.getWakelock();
wakelock.acquire();
if (MORE_DEBUG) {
Slog.d(TAG, callerLogString);
}
// Prevent lambda from leaking 'this'
TransportManager transportManager = mTransportManager;
OnTaskFinishedListener listener = caller -> {
transportManager.disposeOfTransportClient(transportClient, caller);
wakelock.release();
};
Message msg = backupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj = restoreParamsBuilder.apply(transportClient, listener);
backupHandler.sendMessage(msg);
return 0;
}
// Posted to the handler to tear down a restore session in a cleanly synchronized way
public class EndRestoreRunnable implements Runnable {
UserBackupManagerService mBackupManager;
ActiveRestoreSession mSession;
public EndRestoreRunnable(UserBackupManagerService manager, ActiveRestoreSession session) {
mBackupManager = manager;
mSession = session;
}
public void run() {
// clean up the session's bookkeeping
synchronized (mSession) {
mSession.mEnded = true;
}
// clean up the BackupManagerImpl side of the bookkeeping
// and cancel any pending timeout message
mBackupManager.clearRestoreSession(mSession);
}
}
public synchronized void endRestoreSession() {
if (DEBUG) {
Slog.d(TAG, "endRestoreSession");
}
if (mTimedOut) {
Slog.i(TAG, "Session already timed out");
return;
}
if (mEnded) {
throw new IllegalStateException("Restore session already ended");
}
mBackupManagerService.getBackupHandler().post(
new EndRestoreRunnable(mBackupManagerService, this));
}
}