chromium/third_party/openxr/src_overrides/src/loader/android_utilities.cpp

// Copyright (c) 2020-2023, The Khronos Group Inc.
// Copyright (c) 2020-2021, Collabora, Ltd.
//
// SPDX-License-Identifier:  Apache-2.0 OR MIT
//
// Initial Author: Ryan Pavlik <[email protected]>

#include "android_utilities.h"

#ifdef __ANDROID__
#include <android/log.h>
#include <json/value.h>
#include <openxr/openxr.h>
#include <wrap/android.content.h>
#include <wrap/android.database.h>
#include <wrap/android.net.h>

#include <sstream>
#include <vector>

#define ALOGE(...) \
  __android_log_print(ANDROID_LOG_ERROR, "OpenXR-Loader", __VA_ARGS__)
#define ALOGW(...) \
  __android_log_print(ANDROID_LOG_WARN, "OpenXR-Loader", __VA_ARGS__)
#define ALOGV(...) \
  __android_log_print(ANDROID_LOG_VERBOSE, "OpenXR-Loader", __VA_ARGS__)
#define ALOGI(...) \
  __android_log_print(ANDROID_LOG_INFO, "OpenXR-Loader", __VA_ARGS__)

namespace openxr_android {
#ifndef XRLOADER_DISABLE_CONTENT_PROVIDERS
using wrap::android::content::ContentUris;
using wrap::android::content::Context;
using wrap::android::database::Cursor;
using wrap::android::net::Uri;
using wrap::android::net::Uri_Builder;

// Code in here corresponds roughly to the Java "BrokerContract" class and
// subclasses.
namespace {
constexpr auto AUTHORITY = "org.khronos.openxr.runtime_broker";
constexpr auto SYSTEM_AUTHORITY = "org.khronos.openxr.system_runtime_broker";
constexpr auto BASE_PATH = "openxr";
constexpr auto ABI_PATH = "abi";
constexpr auto RUNTIMES_PATH = "runtimes";

constexpr const char* getBrokerAuthority(bool systemBroker) {
  return systemBroker ? SYSTEM_AUTHORITY : AUTHORITY;
}

struct BaseColumns {
  /**
   * The unique ID for a row.
   */
  [[maybe_unused]] static constexpr auto ID = "_id";
};

/**
 * Contains details for the /openxr/[major_ver]/abi/[abi]/runtimes/active URI.
 * <p>
 * This URI represents a "table" containing at most one item, the currently
 * active runtime. The policy of which runtime is chosen to be active (if more
 * than one is installed) is left to the content provider. <p> No sort order is
 * required to be honored by the content provider.
 */
namespace active_runtime {
/**
 * Final path component to this URI.
 */
static constexpr auto TABLE_PATH = "active";

/**
 * Create a content URI for querying the data on the active runtime for a
 * given major version of OpenXR.
 *
 * @param systemBroker If the system runtime broker (instead of the installable
 * one) should be queried.
 * @param majorVer The major version of OpenXR.
 * @param abi The Android ABI name in use.
 * @return A content URI for a single item: the active runtime.
 */
static Uri makeContentUri(bool systemBroker,
                          int majorVersion,
                          const char* abi) {
  auto builder = Uri_Builder::construct();
  builder.scheme("content")
      .authority(getBrokerAuthority(systemBroker))
      .appendPath(BASE_PATH)
      .appendPath(std::to_string(majorVersion))
      .appendPath(ABI_PATH)
      .appendPath(abi)
      .appendPath(RUNTIMES_PATH)
      .appendPath(TABLE_PATH);
  ContentUris::appendId(builder, 0);
  return builder.build();
}

struct Columns : BaseColumns {
  /**
   * Constant for the PACKAGE_NAME column name
   */
  static constexpr auto PACKAGE_NAME = "package_name";

  /**
   * Constant for the NATIVE_LIB_DIR column name
   */
  static constexpr auto NATIVE_LIB_DIR = "native_lib_dir";

  /**
   * Constant for the SO_FILENAME column name
   */
  static constexpr auto SO_FILENAME = "so_filename";

  /**
   * Constant for the HAS_FUNCTIONS column name.
   * <p>
   * If this column contains true, you should check the /functions/ URI for that
   * runtime.
   */
  static constexpr auto HAS_FUNCTIONS = "has_functions";
};
}  // namespace active_runtime

/**
 * Contains details for the
 * /openxr/[major_ver]/abi/[abi]/runtimes/[package]/functions URI. <p> This URI
 * is for package-specific function name remapping. Since this is an optional
 * field in the corresponding JSON manifests for OpenXR, it is optional here as
 * well. If the active runtime contains "true" in its "has_functions" column,
 * then this table must exist and be queryable. <p> No sort order is required to
 * be honored by the content provider.
 */
namespace functions {
/**
 * Final path component to this URI.
 */
static constexpr auto TABLE_PATH = "functions";

/**
 * Create a content URI for querying all rows of the function remapping data for
 * a given runtime package and major version of OpenXR.
 *
 * @param systemBroker If the system runtime broker (instead of the installable
 * one) should be queried.
 * @param majorVer    The major version of OpenXR.
 * @param packageName The package name of the runtime.
 * @param abi The Android ABI name in use.
 * @return A content URI for the entire table: the function remapping for that
 * runtime.
 */
static Uri makeContentUri(bool systemBroker,
                          int majorVersion,
                          std::string const& packageName,
                          const char* abi) {
  auto builder = Uri_Builder::construct();
  builder.scheme("content")
      .authority(getBrokerAuthority(systemBroker))
      .appendPath(BASE_PATH)
      .appendPath(std::to_string(majorVersion))
      .appendPath(ABI_PATH)
      .appendPath(abi)
      .appendPath(RUNTIMES_PATH)
      .appendPath(packageName)
      .appendPath(TABLE_PATH);
  return builder.build();
}

struct Columns : BaseColumns {
  /**
   * Constant for the FUNCTION_NAME column name
   */
  static constexpr auto FUNCTION_NAME = "function_name";

  /**
   * Constant for the SYMBOL_NAME column name
   */
  static constexpr auto SYMBOL_NAME = "symbol_name";
};
}  // namespace functions

}  // namespace

static inline jni::Array<std::string> makeArray(
    std::initializer_list<const char*>&& list) {
  auto ret = jni::Array<std::string>{(long)list.size()};
  long i = 0;
  for (auto&& elt : list) {
    ret.setElement(i, elt);
    ++i;
  }
  return ret;
}
static constexpr auto TAG = "OpenXR-Loader";

#if defined(__arm__)
static constexpr auto ABI = "armeabi-v7l";
#elif defined(__aarch64__)
static constexpr auto ABI = "arm64-v8a";
#elif defined(__i386__)
static constexpr auto ABI = "x86";
#elif defined(__x86_64__)
static constexpr auto ABI = "x86_64";
#else
#error "Unknown ABI!"
#endif

/// Helper class to generate the jsoncpp object corresponding to a synthetic
/// runtime manifest.
class JsonManifestBuilder {
 public:
  JsonManifestBuilder(const std::string& libraryPathParent,
                      const std::string& libraryPath);
  JsonManifestBuilder& function(const std::string& functionName,
                                const std::string& symbolName);

  Json::Value build() const { return root_node; }

 private:
  Json::Value root_node;
};

inline JsonManifestBuilder::JsonManifestBuilder(
    const std::string& libraryPathParent,
    const std::string& libraryPath)
    : root_node(Json::objectValue) {
  root_node["file_format_version"] = "1.0.0";
  root_node["instance_extensions"] = Json::Value(Json::arrayValue);
  root_node["functions"] = Json::Value(Json::objectValue);
  root_node[libraryPathParent] = Json::objectValue;
  root_node[libraryPathParent]["library_path"] = libraryPath;
}

inline JsonManifestBuilder& JsonManifestBuilder::function(
    const std::string& functionName,
    const std::string& symbolName) {
  root_node["functions"][functionName] = symbolName;
  return *this;
}

static constexpr const char* getBrokerTypeName(bool systemBroker) {
  return systemBroker ? "system" : "installable";
}

static int populateFunctions(wrap::android::content::Context const& context,
                             bool systemBroker,
                             const std::string& packageName,
                             JsonManifestBuilder& builder) {
  jni::Array<std::string> projection = makeArray(
      {functions::Columns::FUNCTION_NAME, functions::Columns::SYMBOL_NAME});

  auto uri = functions::makeContentUri(
      systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), packageName, ABI);
  ALOGI("populateFunctions: Querying URI: %s", uri.toString().c_str());

  Cursor cursor = context.getContentResolver().query(uri, projection);

  if (cursor.isNull()) {
    ALOGE("Null cursor when querying content resolver for functions.");
    return -1;
  }
  if (cursor.getCount() < 1) {
    ALOGE(
        "Non-null but empty cursor when querying content resolver for "
        "functions.");
    cursor.close();
    return -1;
  }
  auto functionIndex = cursor.getColumnIndex(functions::Columns::FUNCTION_NAME);
  auto symbolIndex = cursor.getColumnIndex(functions::Columns::SYMBOL_NAME);
  while (cursor.moveToNext()) {
    builder.function(cursor.getString(functionIndex),
                     cursor.getString(symbolIndex));
  }

  cursor.close();
  return 0;
}

/// Get cursor for active runtime, parameterized by whether or not we use the
/// system broker
static bool getActiveRuntimeCursor(
    wrap::android::content::Context const& context,
    jni::Array<std::string> const& projection,
    bool systemBroker,
    Cursor& cursor) {
  auto uri = active_runtime::makeContentUri(
      systemBroker, XR_VERSION_MAJOR(XR_CURRENT_API_VERSION), ABI);
  ALOGI("getActiveRuntimeCursor: Querying URI: %s", uri.toString().c_str());
  try {
    cursor = context.getContentResolver().query(uri, projection);
  } catch (const std::exception& e) {
    ALOGW("Exception when querying %s content resolver: %s",
          getBrokerTypeName(systemBroker), e.what());
    cursor = {};
    return false;
  }

  if (cursor.isNull()) {
    ALOGW("Null cursor when querying %s content resolver.",
          getBrokerTypeName(systemBroker));
    cursor = {};
    return false;
  }
  if (cursor.getCount() < 1) {
    ALOGW("Non-null but empty cursor when querying %s content resolver.",
          getBrokerTypeName(systemBroker));
    cursor.close();
    cursor = {};
    return false;
  }
  return true;
}
#endif // #ifndef XRLOADER_DISABLE_CONTENT_PROVIDERS

int getActiveRuntimeVirtualManifest(
    wrap::android::content::Context const& context,
    Json::Value& virtualManifest) {
#if defined(XRLOADER_DISABLE_CONTENT_PROVIDERS)
  // If we want to ignore android content providers, then just return failure.
  // TODO(alcooper): This will add some extra error logging. When upstreaming
  // this change, we should probably wrap this level as well as the caller in
  // runtime_interface.cpp and then that's corresponding caller in
  // manifest_file.cpp
  return -1;
#else
  jni::Array<std::string> projection =
      makeArray({active_runtime::Columns::PACKAGE_NAME,
                 active_runtime::Columns::NATIVE_LIB_DIR,
                 active_runtime::Columns::SO_FILENAME,
                 active_runtime::Columns::HAS_FUNCTIONS});

  // First, try getting the installable broker's provider
  bool systemBroker = false;
  Cursor cursor;
  if (!getActiveRuntimeCursor(context, projection, systemBroker, cursor)) {
    // OK, try the system broker as a fallback.
    systemBroker = true;
    getActiveRuntimeCursor(context, projection, systemBroker, cursor);
  }

  if (cursor.isNull()) {
    // Couldn't find either broker
    ALOGE("Could access neither the installable nor system runtime broker.");
    return -1;
  }

  cursor.moveToFirst();

  auto filename = cursor.getString(
      cursor.getColumnIndex(active_runtime::Columns::SO_FILENAME));
  auto libDir = cursor.getString(
      cursor.getColumnIndex(active_runtime::Columns::NATIVE_LIB_DIR));
  auto packageName = cursor.getString(
      cursor.getColumnIndex(active_runtime::Columns::PACKAGE_NAME));

  auto hasFunctions = cursor.getInt(cursor.getColumnIndex(
                          active_runtime::Columns::HAS_FUNCTIONS)) == 1;
  __android_log_print(ANDROID_LOG_INFO, TAG,
                      "Got runtime: package: %s, so filename: %s, native lib "
                      "dir: %s, has functions: %s",
                      packageName.c_str(), filename.c_str(), libDir.c_str(),
                      (hasFunctions ? "yes" : "no"));

  auto lib_path = libDir + "/" + filename;
  cursor.close();

  JsonManifestBuilder builder{"runtime", lib_path};
  if (hasFunctions) {
    int result = populateFunctions(context, systemBroker, packageName, builder);
    if (result != 0) {
      return result;
    }
  }
  virtualManifest = builder.build();
  return 0;
#endif  // XRLOADER_DISABLE_CONTENT_PROVIDERS
}
}  // namespace openxr_android

#endif  // __ANDROID__