blob: 46bd31bfdc5e7c0bfe930b836f722dc393c560d8 [file] [log] [blame]
// Copyright (C) 2016 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.
#include "android/emulation/control/ApkInstaller.h"
#include "android/base/StringFormat.h"
#include "android/base/Uuid.h"
#include "android/base/files/PathUtils.h"
#include "android/base/system/System.h"
#include "android/base/threads/Async.h"
#include <fstream>
#include <iostream>
#include <sstream>
namespace android {
namespace emulation {
using android::base::Looper;
using android::base::ParallelTask;
using android::base::PathUtils;
using android::base::StringView;
using android::base::System;
using android::base::Uuid;
using std::string;
using std::vector;
// static
// TODO(birenbaum): what's an appropriate number here? We don't want to go
// forever but some apps are going to be super big and take a long time.
// This is especially troubling since we don't have a reliable way of killing
// the new process besides using the out PID argument and killing that process
// when the user presses cancel (yikes).
const System::Duration ApkInstaller::kInstallTimeoutMs = System::kInfinite;
const char ApkInstaller::kDefaultErrorString[] = "Could not parse error string";
ApkInstaller::ApkInstaller(AdbInterface* adb) : mAdb(adb) {}
ApkInstaller::~ApkInstaller() {
if (mInstallCommand) {
mInstallCommand->cancel();
}
}
// static
bool ApkInstaller::parseOutputForFailure(std::ifstream& stream,
string* outErrorString) {
// "adb install" does not return a helpful exit status, so instead we parse
// the output of the process looking for:
// - "adb: error: failed to copy" in the case that the apk could not be
// copied because the device is full
// - "Failure [<some error code>]", in the case that the apk could not be
// installed because of a specific error
if (!stream) {
*outErrorString = kDefaultErrorString;
return false;
}
string line;
while (getline(stream, line)) {
if (!line.compare(0, 7, "Failure")) {
auto openBrace = line.find("[");
auto closeBrace = line.find("]", openBrace + 1);
if (openBrace != string::npos && closeBrace != string::npos) {
*outErrorString =
line.substr(openBrace + 1, closeBrace - openBrace - 1);
} else {
*outErrorString = kDefaultErrorString;
}
return false;
} else if (!line.compare(0, 26, "adb: error: failed to copy")) {
*outErrorString = "No space left on device";
return false;
}
}
return true;
}
AdbCommandPtr ApkInstaller::install(
android::base::StringView apkFilePath,
ApkInstaller::ResultCallback resultCallback) {
if (!base::System::get()->pathCanRead(apkFilePath)) {
resultCallback(Result::kApkPermissionsError, "");
return nullptr;
}
std::vector<std::string> installCommand{"install", "-r", apkFilePath};
mInstallCommand = mAdb->runAdbCommand(
installCommand,
[resultCallback, this](const OptionalAdbCommandResult& result) {
if (!result || result->exit_code) {
resultCallback(Result::kAdbConnectionFailed, "");
} else {
std::string errorString;
const bool parseResult = parseOutputForFailure(
*(result->output), &errorString);
resultCallback(parseResult ? Result::kSuccess
: Result::kInstallFailed,
errorString);
}
mInstallCommand.reset();
},
kInstallTimeoutMs, true);
return mInstallCommand;
}
} // namespace emulation
} // namespace android