GrabDuck

Изучаем принципы взаимодействия Ubuntu Touch и Android

:


Привет, хабр.

Пару месяцев назад я занимался портированием Ubuntu Touch на платформу Allwinner A10,
в процессе делал заметки себе на память. Сейчас, на мой взгляд, они всё ещё актуальны, пока Ubuntu Touch окончательно не переехала на свой графический сервер Mir и так далее.

Данная статья поможет заинтересованным лицам найти стартовую точку, с которой можно начать близкое знакомство с UT.

Стиль изложения далёк от технического, но если вы не против, то
приглашаю под кат.

Введение


Что такое libhybris

libhybris — прослойка, позволяющая подгружать в Glibc userspace библиотеки из Bionic userspace, на лету заменяя некоторые символы вариантами из Glibc. Проще говоря, данное решение позволяет использовать проприетарные библиотеки для Android в Linux-пространстве. Наибóльшая польза, конечно же, в возможности использовать проприетарные GPU-драйвера, собранные производителем только под Android.
Что такое surfaceflinger

surfaceflinger — нативный сервис андроида, композитный менеджер графических слоёв.

Более подробно про Binder IPC и SurfaceFlinger:

Ubuntu Touch

Ubuntu Touch Developer Preview сама по себе основана на Android, заимствует необходимые сервисы для работы с железом. Общий обзор зависимости можно почитать здесь — Ubuntu Touch Porting или в заметке на OpenNet.

В качестве базовой операционной системы используется обычный Android JB 4.2, а точнее CyagenMod-10.1 (репозиторий подпроектов CM — phablet.ubuntu.com/gitweb). Из него удалено всё что связано с dalvik и java — оставлена только нативная часть, состоящая из системных сервисов и HAL. При желании можно использовать AOSP 4.1, но будьте готовы к адаптации под нативное API от 4.1, оно не покрыто никакой документацией и тем более спецификацией и меняется от релиза к релизу.

Компоненты UT располагаются в chroot, используется самописная утилита uchroot, отрывок:

static int ubuntum(void *a) {
    /* Chroot */
    chroot("/data/ubuntu");
    chdir("/");

    /* Set basic env variables */
    char *const envp[8] = {
        "container=aal",
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 
        "SHELL=/bin/bash", 
        "HOME=/root",
        "USER=root",
        "USERNAME=root",
        "LOGNAME=root", 
        NULL
    };

    /* Exec shell */
    execle("/sbin/init", "/sbin/init", "--verbose", NULL, envp);

    return 0;
}

Для взаимодействия Android-окружения и chroot-окружения Ubuntu задействован механизм libhybris.

Компоненты Ubuntu Touch


Суп из различных компонентов UT можно наблюдать в lanchpad-репозитории ~phablet-team.
Нас интересуют следующие два компонента, отвечающие за работу платформы на Android устройствах:

Скачаем последнюю версию исходников:
bzr branch lp:platform-api
bzr branch lp:qtubuntu
Ubuntu Platform API

Ubuntu platform API — низкоуровневый API для выполнения базовых операций с использованием возможностей платформы (Android).

Примеры методов:

  • ubuntu_application_ui_show_surface
  • ubuntu_application_ui_hide_surface
  • ubuntu_application_ui_move_surface_to
  • ubuntu_application_ui_resize_surface_to

Из файла документации doc/mainpage.md узнаём, что дерево исходников platform-api можно разбить на две части:
  • include — абстрактная декларация platform API
  • android — реализация platform API под Android (я бы уточнил — под Android 4.2)

И то единственное, на что может полагаться сторонний разработчик при работе с этим API — это заголовки из
директории includes/ubuntu/application, а всё остальное предполагает изменяться со временем.

Из файла debian/control узнаём, что:

Package: libplatform-api1-hybris
Depends: libhybris
Description:
Hybris implementation of the Platform API (runtime)
This package provides the hybris implementation of the Platform API.
  
The produced library should be used via libhybris, to communicate with the
Android userspace, which is where the Ubuntu Application Manager lives.

Ага, значит, судя по android/hybris/Android.mk, реализация platform API собирается в виде библотеки libubuntu_application_api с линковкой с нативными либами андроида и помещается в android userspace:
LOCAL_SRC_FILES := \
    ubuntu_application_api_for_hybris.cpp \
    ubuntu_application_gps_for_hybris.cpp \
    ubuntu_application_sensors_for_hybris.cpp \
    ../default/default_ubuntu_application_sensor.cpp \
    ../default/default_ubuntu_application_ui.cpp \
    ../default/default_ubuntu_ui.cpp \
    application_manager.cpp

LOCAL_MODULE := libubuntu_application_api

LOCAL_SHARED_LIBRARIES := \
    libandroidfw \
    libbinder \
    libutils \
    libgui \
    libEGL \
    libGLESv2 \
    libhardware \
    libhardware_legacy

Без внимания осталась директория platform-api/src/android, рассмотрим её в деталях. Судя по наличию файла CMakeLists.txt, сборка идёт уже для glibc.

Есть один-единственный файл с кодом — ubuntu_application_api.cpp, заглянув в которой мы увидим:

extern void *android_dlopen(const char *filename, int flag);
extern void *android_dlsym(void *handle, const char *symbol);
— использование процедур libhybris для динамической загрузки символов из shared-либы из android userspace.
struct Bridge {
    static const char* path_to_library() {
        return "/system/lib/libubuntu_application_api.so";
    }
    Bridge() : lib_handle(android_dlopen(path_to_library(), RTLD_LAZY)) {
        assert(lib_handle && "Error loading ubuntu_application_api");
    }

.......

    void* resolve_symbol(const char* symbol) const {
        return android_dlsym(lib_handle, symbol);
    }
    void* lib_handle;
};
— нехитрый мост для подгрузки символов из libubuntu_application_api.so, которая ладит с нативными сервисами андроида, и которую мы совсем недавно мысленно «собрали» с помощью android/hybris/Android.mk.
#define IMPLEMENT_VOID_FUNCTION3(symbol, arg1, arg2, arg3)      \
    void symbol(arg1 _1, arg2 _2, arg3 _3) {                    \
        static void (*f)(arg1, arg2, arg3) = NULL;              \
        DLSYM(&f, #symbol);                                     \
        f(_1, _2, _3); }
.......

IMPLEMENT_VOID_FUNCTION2(ubuntu_application_ui_init, int, char**);
IMPLEMENT_FUNCTION0(StageHint, ubuntu_application_ui_setup_get_stage_hint);
IMPLEMENT_FUNCTION0(FormFactorHint, ubuntu_application_ui_setup_get_form_factor_hint);
IMPLEMENT_VOID_FUNCTION1(ubuntu_application_ui_start_a_new_session, SessionCredentials*);
IMPLEMENT_VOID_FUNCTION2(ubuntu_application_ui_set_clipboard_content, void*, size_t);
.......
— куча обёрток для символов API, реализованных в libubuntu_application_api.so.

Итак, во избежание путаницы:

  • libubuntu_application_api.so — библиотека под bionic, живёт в android userspace;
  • libubuntu_application_api.so — библиотека под glibc, живёт в linux userpace (chroot), грузит символы из первой через libhybris.

Разработчики решили уменьшить энтропию вселенной путём создания одноимённых библиотек.
Если посмотреть их дебаты по поводу именования компонентов merge-153874 discussion, то уши вянут.
Ubuntu Application Manager

В platform-api/android/hybris помимо реализации Ubuntu platform API находятся исходники ubuntuappmanager — сервиса приложений Ubuntu, он живёт в android userspace и, судя по Android.mk, активно использует libubuntu_application_api и общается через Binder IPC с сервисами андроида.
LOCAL_SRC_FILES:= \
    application_manager.cpp \
    default_application_manager.cpp \

LOCAL_MODULE:= ubuntuappmanager
LOCAL_MODULE_TAGS := optional

LOCAL_SHARED_LIBRARIES := \
    libbinder \
    libinput \
    libgui \
    libskia \
    libandroidfw \
    libutils \
    libEGL \
    libGLESv2 \
    libubuntu_application_api

Решает кучу задач управления приложениями и сессиями, быстрый взгляд на default_application_manager.h:
    void update_app_lists();
    void binderDied(const android::wp<android::IBinder>& who);
    void register_a_surface(...);
    void request_fullscreen(...);
    int get_session_pid(const android::sp<android::IApplicationManagerSession>& session);
    void focus_running_session_with_id(int id);
    void unfocus_running_sessions();
    int32_t query_snapshot_layer_for_session_with_id(int id);
    android::IApplicationManagerSession::SurfaceProperties query_surface_properties_for_session_id(int id);
    void switch_focused_application_locked(size_t index_of_next_focused_app);
    void switch_focus_to_next_application_locked();
    void kill_focused_application_locked();

    void start_a_new_session(
        int32_t session_type,
        int32_t stage_hint,
        const android::String8& app_name,
        const android::String8& desktop_file,
        const android::sp<android::IApplicationManagerSession>& session,
        int fd);

QtUbuntu

Разбираемся с частью UT, отвечающей за взаимодействие между Ubuntu platform API и Qt/QML приложениями.

Если вы не знакомы с Qt Platform Abstraction, то, в кратце, это возможность абстрагироваться от платформы, на которой запускаются приложения Qt с помощью специально написанных QPA-плагинов.

В QPA-плагине реализуются базовые методы вроде createPlatformWindow, а затем Qt приложение, когда захочет создать окошко, использует символ createPlatformWindow из плагина абстракции и в ус не дует, куда оно там дальше пошло.

В данном случае мы будем иметь дело с QPA плагином для работы с Ubuntu application API.

~/ubuntu/qtubuntu $ tree
.
├── qtubuntu.pro
├── src
│   ├── modules
│   │   ├── application   <------------------ QML plugin для общения с
│   │   │   ├── application.cc               | Ubuntu Application Manager
│   │   │   ├── application.h                | из QtQuick
│   │   │   ├── application.pro              |
│   │   │   ├── application_image.cc         |
│   │   │   ├── application_image.h          |
│   │   │   ├── application_list_model.cc    |
│   │   │   ├── application_list_model.h     |
│   │   │   ├── application_manager.cc       |
│   │   │   ├── application_manager.h        |
│   │   │   ├── application_window.cc        |
│   │   │   ├── application_window.h         |
│   │   │   ├── input_filter_area.cc         |
│   │   │   ├── input_filter_area.h          |
│   │   │   ├── logging.h                    |
│   │   │   ├── plugin.cc                    |
│   │   │   └── qmldir                       |
│   │   ├── ----------------------------------
│   │   └── modules.pro
│   ├── platforms
│   │   ├── base
│   │   ├── platforms.pro
│   │   └── ubuntu   <-------------- QPA платформа абстракции
│   │         ├── clipboard.cc
│   │         ├── clipboard.h
│   │         ├── input.cc
│   │         ├── input.h
│   │         ├── integration.cc <-- здесь реализован `createPlatformWindow` например
│   │         ├── integration.h
│   │         ├── main.cc
│   │         ├── screen.cc
│   │         ├── screen.h
│   │         ├── ubuntu.json
│   │         ├── ubuntu.pro
│   │         ├── window.cc
│   │         └── window.h
│   └── src.pro
└── tests

Судя по содержимому ubuntu.pro, платформа линкуется с glibc-версией libubuntu_application_api.so
Обратим внимание на следующие вызовы методов из набора platform API, использованные в integration.cc и window.cc:
#include <ubuntu/application/ui/ubuntu_application_ui.h>

ubuntu_application_ui_start_a_new_session(&credentials);
ubuntu_application_ui_destroy_surface(surface_);
ubuntu_application_ui_create_surface(&surface_, "QUbuntuWindow", geometry.width(), geometry.height(), static_cast<SurfaceRole>(role), flags, eventCallback, this);
ubuntu_application_ui_move_surface_to(surface_, geometry.x(), geometry.y());
ubuntu_application_ui_request_fullscreen_for_surface(surface_);
ubuntu_application_ui_move_surface_to(surface_, rect.x(), rect.y());
ubuntu_application_ui_resize_surface_to(surface_, rect.width(), rect.height());
ubuntu_application_ui_request_fullscreen_for_surface(surface_);
ubuntu_application_ui_show_surface(surface_);
ubuntu_application_ui_hide_surface(surface_);

Теперь понятно, что когда наше Qt приложение захочет создать окошко, то оно вызовет метод из QPA платформы qubuntuQUbuntuIntegration::createPlatformWindow из файла integration.cc:
QPlatformWindow* QUbuntuIntegration::createPlatformWindow(QWindow* window) {
.......
  // Create the window.
  QPlatformWindow* platformWindow = new QUbuntuWindow(.......);
.......
}

Заглядывая в конструктор QUbuntuWindow в файле window.cc, находим вызов метода QUbuntuWindow::createWindow():
void QUbuntuWindow::createWindow() {
.......
  ubuntu_application_ui_create_surface(
      &surface_, "QUbuntuWindow", geometry.width(), geometry.height(),
      static_cast<SurfaceRole>(role), flags, eventCallback, this);
.......
  ubuntu_application_ui_move_surface_to(surface_, geometry.x(), geometry.y());
.......
}

Это крайне урезанный код, но суть ясна — делаются вызовы к Ubuntu platform API, которое у нас реализовано в glibc-версии libubuntu_application_api.so, которая, на самом деле, является мостом к bionic-версии libubuntu_application_api.so, код которой лежит в platform-api/android.

Прыгаем?

Прыгнув с помощью grep в нужный файл, попадаем в platform-api/android/default/default_ubuntu_application_ui.cpp:

// Это в ubuntu_application_ui_create_surface
  ubuntu::application::ui::Surface::Ptr surface = session->create_surface(props,
         ubuntu::application::ui::input::Listener::Ptr(new CallbackEventListener(cb, ctx)));

// Это в ubuntu_application_ui_move_surface_to
  auto s = static_cast<Holder<ubuntu::application::ui::Surface::Ptr>*>(surface);
  s->value->move_to(x, y);

Нам осталось открыть матрёшку и найти, как же реализованы ubuntu::application::ui::Session и, соответственно, ubuntu::application::ui::Surface. А реализованы они в этом файле — ubuntu_application_api_for_hybris.cpp:
namespace android {
.......
struct Session : public ubuntu::application::ui::Session, public UbuntuSurface::Observer {
   .......
    Session(.....) {
    ......
    ubuntu::application::ui::Surface::Ptr create_surface(
        const ubuntu::application::ui::SurfaceProperties& props,
        const ubuntu::application::ui::input::Listener::Ptr& listener) {
        .......

        // О, а вот и вызов конструктора. Осталось только перемотать вверх и найти реализацию UbuntuSurface
        UbuntuSurface* surface = new UbuntuSurface(client, client_channel, looper,
            props, listener,this);

         .......

         // 100% это наш клиент, теперь нужно смотреть UbuntuSurface
         return ubuntu::application::ui::Surface::Ptr(surface);
         .......

Перематываем, находим UbuntuSurface:
struct UbuntuSurface : public ubuntu::application::ui::Surface {
    .......
    UbuntuSurface(const sp<SurfaceComposerClient>& client, .......)
    : ubuntu::application::ui::Surface(listener)
    {
        // Вот это место - прямое обращение к Android API
        surface_control = client->createSurface(
                              String8(props.title),
                              props.width,
                              props.height,
                              PIXEL_FORMAT_RGBA_8888,
                              props.flags & .......);

        surface = surface_control->getSurface();
        .......

Получаем некий объект типа android::SurfaceControl, который является результатом вызова android::SurfaceComposerClient()->createSurface().
Через него проходят все обращения к android::SurfaceComposerClient ( frameworks/native/libs/gui/Surface.cpp), такие как: изменение размеров, перемещение, изменение порядка слоёв и так далее.

Возвращаясь назад по цепочке, понимаем, что же на самом деле происходит, когда мы запускаем очередное Qt приложение с QPA платформой Ubuntu.

Заключение


На этом моменте я вынужден себя остановить, поскольку, на мой взгляд, рассмотренный принцип взаимодействия Ubuntu Touch и Android является самодостаточным. Дальнейшие рассуждения могут идти уже в отрыве от всего вышеописанного. Нерасмотренными остались вопросы взаимодействия qmlscene и ubuntuappmanager, принцип контроля ввода с помощью сервисов SurfaceFlinger и InputDispatcher и другие вопросы из уголков этой простороной темы. Но это уже совсем другая история.

Через неделю приедет телефон на Firefox OS, распотрошу его…