summaryrefslogtreecommitdiff
path: root/files/src/android/goldfish/avd/Avd.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'files/src/android/goldfish/avd/Avd.cpp')
-rw-r--r--files/src/android/goldfish/avd/Avd.cpp234
1 files changed, 234 insertions, 0 deletions
diff --git a/files/src/android/goldfish/avd/Avd.cpp b/files/src/android/goldfish/avd/Avd.cpp
new file mode 100644
index 0000000..9f61f7e
--- /dev/null
+++ b/files/src/android/goldfish/avd/Avd.cpp
@@ -0,0 +1,234 @@
+// Copyright 2024 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/goldfish/avd/Avd.h"
+
+#include <filesystem>
+#include <memory>
+#include <regex>
+#include <unordered_map>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/strings/str_format.h"
+#include "android/base/system/System.h"
+#include "android/goldfish/ConfigDirs.h"
+#include "android/goldfish/IniFile.h"
+#include "android/goldfish/avd/keys.h"
+
+/* technical note on how all of this is supposed to work:
+ *
+ * Each AVD corresponds to a "content directory" that is used to
+ * store persistent disk images and configuration files. Most remarkable
+ * are:
+ *
+ * - a "config.ini" file used to hold configuration information for the
+ * AVD
+ *
+ * - mandatory user data image ("userdata-qemu.img") and cache image
+ * ("cache.img")
+ *
+ * - optional mutable system image ("system-qemu.img"), kernel image
+ * ("kernel-qemu") and read-only ramdisk ("ramdisk.img")
+ *
+ * When starting up an AVD, the emulator looks for relevant disk images
+ * in the content directory. If it doesn't find a given image there, it
+ * will try to search in the list of system directories listed in the
+ * 'config.ini' file through one of the following (key,value) pairs:
+ *
+ * images.sysdir.1 = <first search path>
+ * images.sysdir.2 = <second search path>
+ *
+ * The search paths can be absolute, or relative to the root SDK installation
+ * path (which is determined from the emulator program's location, or from the
+ * ANDROID_SDK_ROOT environment variable).
+ *
+ * Individual image disk search patch can be over-riden on the command-line
+ * with one of the usual options.
+ */
+namespace android::goldfish {
+
+using android::base::System;
+using PropertyList = const std::array<std::string, 3>;
+using AvdFlavor = android::goldfish::Avd::Flavor;
+
+static const std::string_view
+ _imageFileNames[static_cast<int>(AvdImageType::AVD_IMAGE_MAX)] = {
+#define _AVD_IMG(x, y, z) y,
+ AVD_IMAGE_LIST
+#undef _AVD_IMG
+};
+
+static std::string getIconForFlavor(AvdFlavor flavor) {
+ switch (flavor) {
+ case AvdFlavor::PHONE:
+ return "📱"; // 📱 (Smartphone)
+ case AvdFlavor::TV:
+ return "📺"; // 📺 (Television)
+ case AvdFlavor::WEAR:
+ return "⌚️"; // ⌚️ (Smartwatch)
+ case AvdFlavor::ANDROID_AUTO:
+ return "🚗"; // 🚗 (Car)
+ case AvdFlavor::DESKTOP:
+ return "🖥️"; // 🖥️ (Desktop computer)
+ default:
+ return "🤷"; // 🤷 (Unknown)
+ }
+}
+
+absl::StatusOr<Avd> Avd::fromName(std::string name) {
+ auto directory_path = ConfigDirs::getAvdRootDirectory();
+ return Avd::parse(directory_path / (name + ".ini"), name);
+}
+
+AvdFlavor Avd::getFlavor() {
+ if (mFlavor.has_value()) {
+ return mFlavor.value();
+ }
+
+ AvdFlavor res = AvdFlavor::OTHER;
+
+ const std::unordered_map<std::string, Flavor> labelMap{
+ {"phone", Flavor::PHONE}, {"atv", Flavor::TV},
+ {"wear", Flavor::WEAR}, {"aw", Flavor::WEAR},
+ {"car", Flavor::ANDROID_AUTO}, {"pc", Flavor::DESKTOP}};
+
+ const PropertyList props = {"ro.product.name", "ro.product.system.name",
+ "ro.build.flavor"};
+
+ auto sysimg = getSystemImagePath(AvdImageType::BUILDPROP);
+ if (!sysimg.ok()) {
+ dwarning("Unable to retrieve image path: %s, using unknown avd flavor",
+ sysimg.status().message());
+ return AvdFlavor::OTHER;
+ }
+
+ auto buildprop = sysimg.value() / "build.prop";
+ if (!System::get()->pathExists(buildprop) ||
+ !System::get()->pathCanRead(buildprop)) {
+ dwarning("Unable to read build properties: %s, using unknown avd flavor",
+ buildprop);
+ return AvdFlavor::OTHER;
+ }
+ IniFile buildIni(buildprop);
+ buildIni.read();
+
+ for (const auto &prop : props) {
+ if (!buildIni.hasKey(prop)) {
+ continue;
+ }
+
+ auto build = buildIni.getString(prop, "_unused");
+ for (const auto &[key, val] : labelMap) {
+ if (build.find(key) != std::string::npos) {
+ // We found the flavor;
+ dinfo("Found %s in %s", key, build);
+ mFlavor = val;
+ return mFlavor.value();
+ }
+ }
+ }
+
+ // Likely unknown.
+ return res;
+}
+
+absl::StatusOr<fs::path> Avd::getImagePath(AvdImageType imgType) {
+ auto possible = mContentPath / _imageFileNames[static_cast<uint8_t>(imgType)];
+ if (System::get()->pathIsFile(possible) &&
+ System::get()->pathCanRead(possible)) {
+ return possible;
+ }
+
+ return getSystemImagePath(imgType);
+}
+
+absl::StatusOr<fs::path> Avd::getSystemImagePath(AvdImageType imgType) {
+ auto sdk = ConfigDirs::getSdkRootDirectory();
+ for (int n = 0; n < MAX_SEARCH_PATHS; n++) {
+ std::string key = absl::StrFormat("%s%d", SEARCH_PREFIX, n);
+ if (!mConfig->hasKey(key)) {
+ continue;
+ }
+ auto path = sdk / mConfig->getString(key, "unused") /
+ _imageFileNames[static_cast<uint8_t>(imgType)];
+
+ if (System::get()->pathExists(path) && System::get()->pathCanRead(path)) {
+ return path;
+ }
+ }
+ return absl::NotFoundError(absl::StrFormat("No system image path (%s) in %s",
+ SEARCH_PREFIX,
+ mConfig->getBackingFile()));
+}
+
+std::string Avd::details() {
+ auto display = mConfig->getString("tag.display", "");
+ auto width = mConfig->getString("hw.lcd.width", "?");
+ auto height = mConfig->getString("hw.lcd.height", "?");
+ auto icon = getIconForFlavor(getFlavor());
+ return absl::StrFormat("%s - (%sx%s) %s %s", mName, width, height, display,
+ icon);
+}
+
+Avd::Avd(fs::path content_path, std::unique_ptr<IniFile> target,
+ std::unique_ptr<IniFile> config, std::string name)
+ : mContentPath(content_path), mTarget(std::move(target)),
+ mConfig(std::move(config)), mName(name) {}
+
+absl::StatusOr<Avd> Avd::parse(fs::path target, std::string name) {
+ auto sys = System::get();
+ if (!sys->pathExists(target) || !sys->pathCanRead(target)) {
+ return absl::NotFoundError("Unable to parse " + name +
+ ", no access to: " + target.string());
+ }
+
+ auto avd_ini = target;
+ auto ini = std::make_unique<IniFile>(avd_ini);
+ if (!ini->read()) {
+ return absl::InternalError("Unable to parse ini file: " + avd_ini.string());
+ }
+
+ auto rel_path = ini->getString("path.rel", "avd/" + name + ".avd");
+ auto content_path = ConfigDirs::getUserDirectory() / rel_path;
+ auto cfg_ini = content_path / "config.ini";
+ if (!sys->pathExists(cfg_ini) || !sys->pathCanRead(cfg_ini)) {
+ return absl::NotFoundError("Unable to parse " + name +
+ ", no access to config: " + cfg_ini.string());
+ }
+
+ auto config = std::make_unique<IniFile>(cfg_ini);
+ if (!config->read()) {
+ return absl::InternalError("Unable to parse ini file: " + cfg_ini.string());
+ }
+ return Avd(content_path, std::move(ini), std::move(config), name);
+}
+
+std::vector<std::string> Avd::list() {
+ std::vector<std::string> avds;
+ auto pattern = std::regex(".*.ini");
+ auto directory_path = ConfigDirs::getAvdRootDirectory();
+
+ for (const auto &entry : fs::directory_iterator(directory_path)) {
+ const auto &filename = entry.path().filename().string();
+
+ // Simple pattern matching
+ if (std::regex_match(filename, pattern)) {
+ std::string name = filename;
+ name.erase(name.size() - 4);
+ avds.push_back(name);
+ }
+ }
+ return avds;
+}
+} // namespace android::goldfish \ No newline at end of file