blob: 97bde9c03440b6b14af4f5d371bf347fc73cc4c4 [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.utils;
import static com.android.server.backup.BackupManagerService.DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.Session;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.restore.RestoreDeleteObserver;
import com.android.server.backup.restore.RestorePolicy;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
/**
* Utility methods used by {@link com.android.server.backup.restore.PerformAdbRestoreTask} and
* {@link com.android.server.backup.restore.FullRestoreEngine}.
*/
public class RestoreUtils {
/**
* Reads apk contents from input stream and installs the apk.
*
* @param instream - input stream to read apk data from.
* @param context - installing context
* @param deleteObserver - {@link RestoreDeleteObserver} instance.
* @param manifestSignatures - manifest signatures.
* @param packagePolicies - package policies.
* @param info - backup file info.
* @param installerPackageName - package name of installer.
* @param bytesReadListener - listener to be called for counting bytes read.
* @return true if apk was successfully read and installed and false otherwise.
*/
// TODO: Refactor to get rid of unneeded params.
public static boolean installApk(InputStream instream, Context context,
RestoreDeleteObserver deleteObserver,
HashMap<String, Signature[]> manifestSignatures,
HashMap<String, RestorePolicy> packagePolicies,
FileMetadata info,
String installerPackageName,
BytesReadListener bytesReadListener,
int userId) {
boolean okay = true;
if (DEBUG) {
Slog.d(TAG, "Installing from backup: " + info.packageName);
}
try {
LocalIntentReceiver receiver = new LocalIntentReceiver();
PackageManager packageManager = context.getPackageManager();
PackageInstaller installer = packageManager.getPackageInstaller();
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.setInstallerPackageName(installerPackageName);
int sessionId = installer.createSession(params);
try {
try (Session session = installer.openSession(sessionId)) {
try (OutputStream apkStream = session.openWrite(info.packageName, 0,
info.size)) {
byte[] buffer = new byte[32 * 1024];
long size = info.size;
while (size > 0) {
long toRead = (buffer.length < size) ? buffer.length : size;
int didRead = instream.read(buffer, 0, (int) toRead);
if (didRead >= 0) {
bytesReadListener.onBytesRead(didRead);
}
apkStream.write(buffer, 0, didRead);
size -= didRead;
}
}
// Installation is current disabled
session.abandon();
// session.commit(receiver.getIntentSender());
}
} catch (Exception t) {
installer.abandonSession(sessionId);
throw t;
}
// Installation is current disabled
Intent result = null;
// Intent result = receiver.getResult();
// Installation is current disabled
int status = PackageInstaller.STATUS_FAILURE;
// int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
// PackageInstaller.STATUS_FAILURE);
if (status != PackageInstaller.STATUS_SUCCESS) {
// The only time we continue to accept install of data even if the
// apk install failed is if we had already determined that we could
// accept the data regardless.
if (packagePolicies.get(info.packageName) != RestorePolicy.ACCEPT) {
okay = false;
}
} else {
// Okay, the install succeeded. Make sure it was the right app.
boolean uninstall = false;
final String installedPackageName = result.getStringExtra(
PackageInstaller.EXTRA_PACKAGE_NAME);
if (!installedPackageName.equals(info.packageName)) {
Slog.w(TAG, "Restore stream claimed to include apk for "
+ info.packageName + " but apk was really "
+ installedPackageName);
// delete the package we just put in place; it might be fraudulent
okay = false;
uninstall = true;
} else {
try {
PackageInfo pkg = packageManager.getPackageInfoAsUser(info.packageName,
PackageManager.GET_SIGNING_CERTIFICATES, userId);
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP)
== 0) {
Slog.w(TAG, "Restore stream contains apk of package "
+ info.packageName
+ " but it disallows backup/restore");
okay = false;
} else {
// So far so good -- do the signatures match the manifest?
Signature[] sigs = manifestSignatures.get(info.packageName);
PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
if (AppBackupUtils.signaturesMatch(sigs, pkg, pmi)) {
// If this is a system-uid app without a declared backup agent,
// don't restore any of the file data.
if (UserHandle.isCore(pkg.applicationInfo.uid)
&& (pkg.applicationInfo.backupAgentName == null)) {
Slog.w(TAG, "Installed app " + info.packageName
+ " has restricted uid and no agent");
okay = false;
}
} else {
Slog.w(TAG, "Installed app " + info.packageName
+ " signatures do not match restore manifest");
okay = false;
uninstall = true;
}
}
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Install of package " + info.packageName
+ " succeeded but now not found");
okay = false;
}
}
// If we're not okay at this point, we need to delete the package
// that we just installed.
if (uninstall) {
deleteObserver.reset();
packageManager.deletePackage(
installedPackageName,
deleteObserver, 0);
deleteObserver.waitForCompletion();
}
}
} catch (IOException e) {
Slog.e(TAG, "Unable to transcribe restored apk for install");
okay = false;
}
return okay;
}
private static class LocalIntentReceiver {
private final Object mLock = new Object();
@GuardedBy("mLock")
private Intent mResult = null;
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
synchronized (mLock) {
mResult = intent;
mLock.notifyAll();
}
}
};
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
public Intent getResult() {
synchronized (mLock) {
while (mResult == null) {
try {
mLock.wait();
} catch (InterruptedException e) {
// ignored
}
}
return mResult;
}
}
}
}