diff --git a/Android.mk b/Android.mk index d476f9e..e7b0220 100644 --- a/Android.mk +++ b/Android.mk @@ -34,6 +34,7 @@ LOCAL_SRC_FILES := \ android/service/platform_service_interface.cpp \ android/service/platform_service.cpp \ android/service/platform_api_stub.cpp \ + android/service/intent.cpp \ src/anbox/common/fd.cpp \ src/anbox/common/wait_handle.cpp \ src/anbox/rpc/message_processor.cpp \ @@ -60,15 +61,6 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_CFLAGS := \ -fexceptions \ -std=c++1y -# Drop a few packages we don't want in our images as they -# are not needed (e.g. a home/launcher application as we -# don't have this concept). -LOCAL_OVERRIDES_PACKAGES := \ - Launcher \ - Launcher2 \ - LatinIME \ - Home \ - QuickSearchBox include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) @@ -107,3 +99,5 @@ include $(BUILD_SHARED_LIBRARY) # Include the Android.mk files below will override LOCAL_PATH so we # have to take a copy of it here. TMP_PATH := $(LOCAL_PATH) + +include $(TMP_PATH)/android/appmgr/Android.mk diff --git a/android/appmgr/Android.mk b/android/appmgr/Android.mk new file mode 100644 index 0000000..e122453 --- /dev/null +++ b/android/appmgr/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-common \ + android-support-v13 +LOCAL_SRC_FILES := $(call all-java-files-under, src) +# LOCAL_SDK_VERSION := current +LOCAL_PACKAGE_NAME := AnboxAppMgr +LOCAL_CERTIFICATE := shared +LOCAL_PRIVILEGED_MODULE := true +LOCAL_OVERRIDES_PACKAGES := \ + Home \ + Launcher2 \ + Launcher3 \ + LatinIME \ + QuickSearchBox +include $(BUILD_PACKAGE) diff --git a/android/appmgr/AndroidManifest.xml b/android/appmgr/AndroidManifest.xml new file mode 100644 index 0000000..7ffbb79 --- /dev/null +++ b/android/appmgr/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/appmgr/res/values/strings.xml b/android/appmgr/res/values/strings.xml new file mode 100644 index 0000000..8926490 --- /dev/null +++ b/android/appmgr/res/values/strings.xml @@ -0,0 +1,3 @@ + + Anbox Application Manager + diff --git a/android/appmgr/src/org/anbox/appmgr/LauncherActivity.java b/android/appmgr/src/org/anbox/appmgr/LauncherActivity.java new file mode 100644 index 0000000..d89b03e --- /dev/null +++ b/android/appmgr/src/org/anbox/appmgr/LauncherActivity.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +package org.anbox.appmgr; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.content.Intent; + +public final class LauncherActivity extends Activity { + private static final String TAG = "AnboxAppMgr"; + + @Override + public void onCreate(Bundle info) { + super.onCreate(info); + + Intent intent = new Intent(this, LauncherService.class); + startService(intent); + + Log.i(TAG, "Created launcher activity"); + } + + @Override + public void onDestroy() { + Log.i(TAG, "Destroyed launcher activity"); + + Intent intent = new Intent(this, LauncherService.class); + stopService(intent); + + super.onDestroy(); + } +} diff --git a/android/appmgr/src/org/anbox/appmgr/LauncherService.java b/android/appmgr/src/org/anbox/appmgr/LauncherService.java new file mode 100644 index 0000000..98657c8 --- /dev/null +++ b/android/appmgr/src/org/anbox/appmgr/LauncherService.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +package org.anbox.appmgr; + +import android.app.Service; +import android.util.Log; +import android.content.Intent; +import android.os.IBinder; + +public final class LauncherService extends Service { + public static final String TAG = "AnboxAppMgr"; + + private PlatformService mPlatformService; + + public LauncherService() { + super(); + Log.i(TAG, "Service created"); + } + + @Override + public void onCreate() { + mPlatformService = new PlatformService(getBaseContext()); + // Send all necessary initial updates + mPlatformService.sendApplicationListUpdate(); + + Log.i(TAG, "Service started"); + } + + @Override + public void onDestroy() { + Log.i(TAG, "Service stopped"); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/android/appmgr/src/org/anbox/appmgr/MainApplication.java b/android/appmgr/src/org/anbox/appmgr/MainApplication.java new file mode 100644 index 0000000..db74d49 --- /dev/null +++ b/android/appmgr/src/org/anbox/appmgr/MainApplication.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +package org.anbox.appmgr; + +import android.app.Application; + +public final class MainApplication extends Application { +} diff --git a/android/appmgr/src/org/anbox/appmgr/PackageEventReceiver.java b/android/appmgr/src/org/anbox/appmgr/PackageEventReceiver.java new file mode 100644 index 0000000..77de857 --- /dev/null +++ b/android/appmgr/src/org/anbox/appmgr/PackageEventReceiver.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +package org.anbox.appmgr; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class PackageEventReceiver extends BroadcastReceiver { + private static final String TAG = "AnboxAppMgr"; + + @Override + public void onReceive(Context context, Intent intent) { + Log.i(TAG, "Received intent " + intent.toString()); + } +} diff --git a/android/appmgr/src/org/anbox/appmgr/PlatformService.java b/android/appmgr/src/org/anbox/appmgr/PlatformService.java new file mode 100644 index 0000000..5ede408 --- /dev/null +++ b/android/appmgr/src/org/anbox/appmgr/PlatformService.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +package org.anbox.appmgr; + +import android.os.ServiceManager; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.Log; +import android.content.Intent; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ApplicationInfo; +import android.net.Uri; + +import java.util.List; + +public final class PlatformService { + private static final String TAG = "AnboxAppMgr"; + private static final String SERVICE_NAME = "org.anbox.PlatformService"; + private static final String DESCRIPTOR = "org.anbox.IPlatformService"; + + private static final int TRANSACTION_updateApplicationList = (IBinder.FIRST_CALL_TRANSACTION + 2); + + private IBinder mService = null; + private PackageManager mPm = null; + + private void connectService() { + if (mService != null) + return; + mService = ServiceManager.getService(SERVICE_NAME); + } + + public PlatformService(Context context) { + if (context != null) { + mPm = context.getPackageManager(); + } else { + Log.w(TAG, "No context available"); + } + + Log.i(TAG, "Connected to platform service"); + } + + public void sendApplicationListUpdate() { + connectService(); + + if (mPm == null || mService == null) + return; + + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(DESCRIPTOR); + + List apps = mPm.getInstalledApplications(0); + data.writeInt(apps.size()); + for (int n = 0; n < apps.size(); n++) { + ApplicationInfo appInfo = apps.get(n); + data.writeString(appInfo.name); + data.writeString(appInfo.packageName); + + Intent launchIntent = mPm.getLaunchIntentForPackage(appInfo.packageName); + if (launchIntent != null) { + data.writeInt(1); + data.writeString(launchIntent.getAction()); + if (launchIntent.getData() != null) + data.writeString(launchIntent.getData().toString()); + else + data.writeString(""); + data.writeString(launchIntent.getType()); + data.writeString(launchIntent.getComponent().getPackageName()); + data.writeString(launchIntent.getComponent().getClassName()); + data.writeInt(launchIntent.getCategories().size()); + for (String category : launchIntent.getCategories()) + data.writeString(category); + } else { + data.writeInt(0); + } + + // FIXME add icon, flags, ... + } + + Parcel reply = Parcel.obtain(); + try { + mService.transact(TRANSACTION_updateApplicationList, data, reply, 0); + } + catch (RemoteException ex) { + Log.w(TAG, "Failed to send updatePackageList request to remote binder service: " + ex.getMessage()); + } + } + + public void notifyPackageAdded(Intent intent) { + } + + public void notifyPackageRemoved(Intent intent) { + } +} diff --git a/android/service/android_api_skeleton.cpp b/android/service/android_api_skeleton.cpp index 63d2f6b..fa388d5 100644 --- a/android/service/android_api_skeleton.cpp +++ b/android/service/android_api_skeleton.cpp @@ -27,6 +27,8 @@ #include +#include + namespace { std::map common_env = { {"ANDROID_DATA", "/data"}, @@ -60,69 +62,60 @@ void AndroidApiSkeleton::connect_services() { } } -void AndroidApiSkeleton::install_application(anbox::protobuf::bridge::InstallApplication const *request, - anbox::protobuf::rpc::Void *response, - google::protobuf::Closure *done) { - (void) response; - - std::vector argv = { - "/system/bin/pm", - "install", - request->path(), - }; - - auto process = core::posix::exec("/system/bin/sh", argv, common_env, core::posix::StandardStream::empty); - wait_for_process(process, response); - - done->Run(); -} - void AndroidApiSkeleton::launch_application(anbox::protobuf::bridge::LaunchApplication const *request, anbox::protobuf::rpc::Void *response, google::protobuf::Closure *done) { (void) response; - std::string intent = request->package_name(); - if (request->has_activity()) { - intent += "/"; - intent += request->activity(); - } + auto intent = request->intent(); std::vector argv = { "/system/bin/am", "start", - // Launch any applications always in freeform stack + // Launch any application always in the freeform stack "--stack", "2", - intent, }; + if (intent.has_action()) { + argv.push_back("-a"); + argv.push_back(intent.action()); + } + + if (intent.has_uri()) { + argv.push_back("-d"); + argv.push_back(intent.uri()); + } + + if (intent.has_type()) { + argv.push_back("-t"); + argv.push_back(intent.type()); + } + + std::string component; + if (intent.has_package()) + component += intent.package(); + if (!component.empty() && intent.has_component()) { + component += "/"; + component += intent.component(); + } + + if (!component.empty()) + argv.push_back(component); + + ALOGI("Launch am with the following arguments: "); + std::string test; + for (const auto &a : argv) { + test += a; + test += " "; + } + ALOGI("%s", test.c_str()); + auto process = core::posix::exec("/system/bin/sh", argv, common_env, core::posix::StandardStream::empty); wait_for_process(process, response); done->Run(); } -void AndroidApiSkeleton::set_dns_servers(anbox::protobuf::bridge::SetDnsServers const *request, - anbox::protobuf::rpc::Void *response, - google::protobuf::Closure *done) { - (void) response; - - std::vector argv = { - "resolver", - "setnetdns", - "0", - request->domain(), - }; - - for (int n = 0; n < request->servers_size(); n++) - argv.push_back(request->servers(n).address()); - - auto process = core::posix::exec("/system/bin/ndc", argv, common_env, core::posix::StandardStream::empty); - wait_for_process(process, response); - - done->Run(); -} - void AndroidApiSkeleton::set_focused_task(anbox::protobuf::bridge::SetFocusedTask const *request, anbox::protobuf::rpc::Void *response, google::protobuf::Closure *done) { diff --git a/android/service/android_api_skeleton.h b/android/service/android_api_skeleton.h index 4cdd582..c4d2fd3 100644 --- a/android/service/android_api_skeleton.h +++ b/android/service/android_api_skeleton.h @@ -49,18 +49,10 @@ public: AndroidApiSkeleton(); ~AndroidApiSkeleton(); - void install_application(anbox::protobuf::bridge::InstallApplication const *request, - anbox::protobuf::rpc::Void *response, - google::protobuf::Closure *done); - void launch_application(anbox::protobuf::bridge::LaunchApplication const *request, anbox::protobuf::rpc::Void *response, google::protobuf::Closure *done); - void set_dns_servers(anbox::protobuf::bridge::SetDnsServers const *request, - anbox::protobuf::rpc::Void *response, - google::protobuf::Closure *done); - void set_focused_task(anbox::protobuf::bridge::SetFocusedTask const *request, anbox::protobuf::rpc::Void *response, google::protobuf::Closure *done); diff --git a/android/service/message_processor.cpp b/android/service/message_processor.cpp index 4036917..0fc2054 100644 --- a/android/service/message_processor.cpp +++ b/android/service/message_processor.cpp @@ -35,12 +35,8 @@ MessageProcessor::~MessageProcessor() { } void MessageProcessor::dispatch(rpc::Invocation const& invocation) { - if (invocation.method_name() == "install_application") - invoke(this, platform_api_.get(), &AndroidApiSkeleton::install_application, invocation); - else if (invocation.method_name() == "launch_application") + if (invocation.method_name() == "launch_application") invoke(this, platform_api_.get(), &AndroidApiSkeleton::launch_application, invocation); - else if (invocation.method_name() == "set_dns_servers") - invoke(this, platform_api_.get(), &AndroidApiSkeleton::set_dns_servers, invocation); else if (invocation.method_name() == "set_focused_task") invoke(this, platform_api_.get(), &AndroidApiSkeleton::set_focused_task, invocation); } diff --git a/android/service/platform_api_stub.cpp b/android/service/platform_api_stub.cpp index fd2d605..85deddf 100644 --- a/android/service/platform_api_stub.cpp +++ b/android/service/platform_api_stub.cpp @@ -61,4 +61,28 @@ void PlatformApiStub::update_window_state(const WindowStateUpdate &state) { rpc_channel_->send_event(seq); } + +void PlatformApiStub::update_application_list(const ApplicationListUpdate &update) { + protobuf::bridge::EventSequence seq; + auto event = seq.mutable_application_list_update(); + + for (const auto &a : update.applications) { + auto app = event->add_applications(); + app->set_name(a.name); + app->set_package(a.package); + + auto launch_intent = app->mutable_launch_intent(); + launch_intent->set_action(a.launch_intent.action); + launch_intent->set_uri(a.launch_intent.uri); + launch_intent->set_type(a.launch_intent.type); + launch_intent->set_package(a.launch_intent.package); + launch_intent->set_component(a.launch_intent.component); + for (const auto &category : a.launch_intent.categories) { + auto c = launch_intent->add_categories(); + *c = category; + } + } + + rpc_channel_->send_event(seq); +} } // namespace anbox diff --git a/android/service/platform_api_stub.h b/android/service/platform_api_stub.h index 436facf..4f14b3a 100644 --- a/android/service/platform_api_stub.h +++ b/android/service/platform_api_stub.h @@ -60,6 +60,25 @@ public: void update_window_state(const WindowStateUpdate &state); + struct ApplicationListUpdate { + struct Application { + std::string name; + std::string package; + struct Intent { + std::string action; + std::string uri; + std::string type; + std::string package; + std::string component; + std::vector categories; + }; + Intent launch_intent; + }; + std::vector applications; + }; + + void update_application_list(const ApplicationListUpdate &update); + private: template struct Request { diff --git a/android/service/platform_service.cpp b/android/service/platform_service.cpp index 321791b..f1e6993 100644 --- a/android/service/platform_service.cpp +++ b/android/service/platform_service.cpp @@ -16,6 +16,7 @@ */ #include "android/service/platform_service.h" +#include "android/service/intent.h" #include "anbox/rpc/channel.h" #include "anbox_rpc.pb.h" @@ -84,4 +85,44 @@ status_t PlatformService::update_window_state(const Parcel &data) { return OK; } + +status_t PlatformService::update_application_list(const Parcel &data) { + anbox::PlatformApiStub::ApplicationListUpdate update; + const auto num_packages = data.readInt32(); + for (auto n = 0; n < num_packages; n++) { + String8 name(data.readString16()); + String8 package_name(data.readString16()); + + auto p = anbox::PlatformApiStub::ApplicationListUpdate::Application{ + name.string(), + package_name.string(), + }; + + if (data.readInt32() == 1) { + String8 action(data.readString16()); + String8 uri(data.readString16()); + String8 type(data.readString16()); + String8 component_package(data.readString16()); + String8 component_class(data.readString16()); + + std::vector categories; + unsigned int num_categories = data.readInt32(); + for (int m = 0; m < num_categories; m++) + categories.push_back(String8(data.readString16()).string()); + + p.launch_intent.action = action; + p.launch_intent.uri = uri; + p.launch_intent.type = type; + p.launch_intent.package = component_package; + p.launch_intent.component = component_class; + p.launch_intent.categories = categories; + + update.applications.push_back(p); + }; + } + + platform_api_stub_->update_application_list(update); + + return OK; +} } // namespace android diff --git a/android/service/platform_service.h b/android/service/platform_service.h index 8a4eadf..6ba6bbf 100644 --- a/android/service/platform_service.h +++ b/android/service/platform_service.h @@ -34,6 +34,7 @@ public: status_t boot_finished() override; status_t update_window_state(const Parcel &data) override; + status_t update_application_list(const Parcel &data) override; private: anbox::PlatformApiStub::WindowStateUpdate::Window unpack_window_state(const Parcel &data); diff --git a/android/service/platform_service_interface.cpp b/android/service/platform_service_interface.cpp index ff2e92c..0df9c14 100644 --- a/android/service/platform_service_interface.cpp +++ b/android/service/platform_service_interface.cpp @@ -34,6 +34,12 @@ status_t BpPlatformService::update_window_state(const Parcel&) { return remote()->transact(IPlatformService::UPDATE_WINDOW_STATE, data, &reply); } +status_t BpPlatformService::update_application_list(const Parcel&) { + Parcel data, reply; + data.writeInterfaceToken(IPlatformService::getInterfaceDescriptor()); + return remote()->transact(IPlatformService::UPDATE_APPLICATION_LIST, data, &reply); +} + IMPLEMENT_META_INTERFACE(PlatformService, "org.anbox.IPlatformService"); status_t BnPlatformService::onTransact(uint32_t code, const Parcel &data, @@ -45,6 +51,9 @@ status_t BnPlatformService::onTransact(uint32_t code, const Parcel &data, case UPDATE_WINDOW_STATE: CHECK_INTERFACE(IPlatformService, data, reply); return update_window_state(data); + case UPDATE_APPLICATION_LIST: + CHECK_INTERFACE(IPlatformService, data, reply); + return update_application_list(data); default: break; } diff --git a/android/service/platform_service_interface.h b/android/service/platform_service_interface.h index 876cf95..dac8643 100644 --- a/android/service/platform_service_interface.h +++ b/android/service/platform_service_interface.h @@ -36,10 +36,12 @@ public: // Keep this synchronized with frameworks/base/services/java/com/android/server/wm/AnboxPlatformServiceProxy.java BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION, UPDATE_WINDOW_STATE = IBinder::FIRST_CALL_TRANSACTION + 1, + UPDATE_APPLICATION_LIST = IBinder::FIRST_CALL_TRANSACTION + 2, }; virtual status_t boot_finished() = 0; virtual status_t update_window_state(const Parcel &data) = 0; + virtual status_t update_application_list(const Parcel &data) = 0; }; class BpPlatformService : public BpInterface { @@ -48,6 +50,7 @@ public: status_t boot_finished() override; status_t update_window_state(const Parcel &data) override; + status_t update_application_list(const Parcel &data) override; }; class BnPlatformService : public BnInterface { diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index f20617c..24ebcab 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(bubblewrap) add_subdirectory(process-cpp-minimal) add_subdirectory(android-emugl) +add_subdirectory(xdg) diff --git a/external/xdg/CMakeLists.txt b/external/xdg/CMakeLists.txt new file mode 100644 index 0000000..57089c3 --- /dev/null +++ b/external/xdg/CMakeLists.txt @@ -0,0 +1,24 @@ +# We have to manually alter the cxx flags to have a working +# travis-ci build. Its container-based infrastructure only features +# a very old cmake that does not support the more current: +# set_property(TARGET xdg_test PROPERTY CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +find_package(Boost COMPONENTS filesystem system unit_test_framework) + +include_directories( + . + ${Boost_INCLUDE_DIRS} +) + +add_library(xdg xdg.cpp) +set_property(TARGET xdg PROPERTY CXX_STANDARD 11) +target_link_libraries(xdg ${Boost_LIBRARIES}) + +enable_testing() +add_definitions(-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN -DBOOST_TEST_MODULE=xdg) +add_executable(xdg_test xdg_test.cpp) +set_property(TARGET xdg_test PROPERTY CXX_STANDARD 11) +target_link_libraries(xdg_test xdg) + +add_test(xdg_test xdg_test) diff --git a/external/xdg/LICENSE b/external/xdg/LICENSE new file mode 100644 index 0000000..341c30b --- /dev/null +++ b/external/xdg/LICENSE @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/external/xdg/xdg.cpp b/external/xdg/xdg.cpp new file mode 100644 index 0000000..de9b043 --- /dev/null +++ b/external/xdg/xdg.cpp @@ -0,0 +1,203 @@ +// Copyright (C) 2015 Thomas Voß +// +// This library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#include + +#include + +#include +#include + +namespace fs = boost::filesystem; + +namespace +{ + +fs::path throw_if_not_absolute(const fs::path& p) +{ + if (p.has_root_directory()) + return p; + + throw std::runtime_error{"Directores MUST be absolute."}; +} + +namespace env +{ +std::string get(const std::string& key, const std::string& default_value) +{ + if (auto value = std::getenv(key.c_str())) + return value; + return default_value; +} + +std::string get_or_throw(const std::string& key) +{ + if (auto value = std::getenv(key.c_str())) + { + return value; + } + + throw std::runtime_error{key + " not set in environment"}; +} + +constexpr const char* xdg_data_home{"XDG_DATA_HOME"}; +constexpr const char* xdg_data_dirs{"XDG_DATA_DIRS"}; +constexpr const char* xdg_config_home{"XDG_CONFIG_HOME"}; +constexpr const char* xdg_config_dirs{"XDG_CONFIG_DIRS"}; +constexpr const char* xdg_cache_home{"XDG_CACHE_HOME"}; +constexpr const char* xdg_runtime_dir{"XDG_RUNTIME_DIR"}; +} + +namespace impl +{ +class BaseDirSpecification : public xdg::BaseDirSpecification +{ +public: + static const BaseDirSpecification& instance() + { + static const BaseDirSpecification spec; + return spec; + } + + BaseDirSpecification() + { + } + + const xdg::Data& data() const override + { + return data_; + } + + const xdg::Config& config() const override + { + return config_; + } + + const xdg::Cache& cache() const override + { + return cache_; + } + + const xdg::Runtime& runtime() const override + { + return runtime_; + } + +private: + xdg::Data data_; + xdg::Config config_; + xdg::Cache cache_; + xdg::Runtime runtime_; +}; +} +} + +fs::path xdg::Data::home() const +{ + auto v = env::get(env::xdg_data_home, ""); + if (v.empty()) + return throw_if_not_absolute(fs::path{env::get_or_throw("HOME")} / ".local" / "share"); + + return throw_if_not_absolute(fs::path(v)); +} + +std::vector xdg::Data::dirs() const +{ + auto v = env::get(env::xdg_data_dirs, ""); + if (v.empty()) + return {fs::path{"/usr/local/share"}, fs::path{"/usr/share"}}; + + std::vector tokens; + tokens = boost::split(tokens, v, boost::is_any_of(":")); + std::vector result; + for (const auto& token : tokens) + { + result.push_back(throw_if_not_absolute(fs::path(token))); + } + return result; +} + +fs::path xdg::Config::home() const +{ + auto v = env::get(env::xdg_config_home, ""); + if (v.empty()) + return throw_if_not_absolute(fs::path{env::get_or_throw("HOME")} / ".config"); + + return throw_if_not_absolute(fs::path(v)); +} + +std::vector xdg::Config::dirs() const +{ + auto v = env::get(env::xdg_config_dirs, ""); + if (v.empty()) + return {fs::path{"/etc/xdg"}}; + + std::vector tokens; + tokens = boost::split(tokens, v, boost::is_any_of(":")); + std::vector result; + for (const auto& token : tokens) + { + fs::path p(token); + result.push_back(throw_if_not_absolute(p)); + } + return result; +} + +fs::path xdg::Cache::home() const +{ + auto v = env::get(env::xdg_cache_home, ""); + if (v.empty()) + return throw_if_not_absolute(fs::path{env::get_or_throw("HOME")} / ".cache"); + + return throw_if_not_absolute(fs::path(v)); +} + +fs::path xdg::Runtime::dir() const +{ + auto v = env::get(env::xdg_config_home, ""); + if (v.empty()) + { + // We do not fall back gracefully and instead throw, dispatching to calling + // code for handling the case of a safe user-specfic runtime directory missing. + throw std::runtime_error{"Runtime directory not set"}; + } + + return throw_if_not_absolute(fs::path(v)); +} + +std::shared_ptr xdg::BaseDirSpecification::create() +{ + return std::make_shared(); +} + +const xdg::Data& xdg::data() +{ + return impl::BaseDirSpecification::instance().data(); +} + +const xdg::Config& xdg::config() +{ + return impl::BaseDirSpecification::instance().config(); +} + +const xdg::Cache& xdg::cache() +{ + return impl::BaseDirSpecification::instance().cache(); +} + +const xdg::Runtime& xdg::runtime() +{ + return impl::BaseDirSpecification::instance().runtime(); +} diff --git a/external/xdg/xdg.h b/external/xdg/xdg.h new file mode 100644 index 0000000..379f0ff --- /dev/null +++ b/external/xdg/xdg.h @@ -0,0 +1,118 @@ +// Copyright (C) 2015 Thomas Voß +// +// This library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +#ifndef XDG_H_ +#define XDG_H_ + +#include + +#include +#include + +namespace xdg +{ +// NotCopyable deletes the copy c'tor and the assignment operator. +struct NotCopyable +{ + NotCopyable() = default; + NotCopyable(const NotCopyable&) = delete; + virtual ~NotCopyable() = default; + NotCopyable& operator=(const NotCopyable&) = delete; +}; + +// NotMoveable deletes the move c'tor and the move assignment operator. +struct NotMoveable +{ + NotMoveable() = default; + NotMoveable(NotMoveable&&) = delete; + virtual ~NotMoveable() = default; + NotMoveable& operator=(NotMoveable&&) = delete; +}; + +// Data provides functions to query the XDG_DATA_* entries. +class Data : NotCopyable, NotMoveable +{ +public: + // home returns the base directory relative to which user specific + // data files should be stored. + virtual boost::filesystem::path home() const; + // dirs returns the preference-ordered set of base directories to + // search for data files in addition to the $XDG_DATA_HOME base + // directory. + virtual std::vector dirs() const; +}; + +// Config provides functions to query the XDG_CONFIG_* entries. +class Config : NotCopyable, NotMoveable +{ +public: + // home returns the base directory relative to which user specific + // configuration files should be stored. + virtual boost::filesystem::path home() const; + // dirs returns the preference-ordered set of base directories to + // search for configuration files in addition to the + // $XDG_CONFIG_HOME base directory. + virtual std::vector dirs() const; +}; + +// Cache provides functions to query the XDG_CACHE_HOME entry. +class Cache : NotCopyable, NotMoveable +{ +public: + // home returns the base directory relative to which user specific + // non-essential data files should be stored. + virtual boost::filesystem::path home() const; +}; + +// Runtime provides functions to query the XDG_RUNTIME_DIR entry. +class Runtime : NotCopyable, NotMoveable +{ +public: + // home returns the base directory relative to which user-specific + // non-essential runtime files and other file objects (such as + // sockets, named pipes, ...) should be stored. + virtual boost::filesystem::path dir() const; +}; + +// A BaseDirSpecification implements the XDG base dir specification: +// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html +class BaseDirSpecification : NotCopyable, NotMoveable +{ +public: + // create returns an Implementation of BaseDirSpecification. + static std::shared_ptr create(); + + // data returns an immutable Data instance. + virtual const Data& data() const = 0; + // config returns an immutable Config instance. + virtual const Config& config() const = 0; + // cache returns an immutable Cache instance. + virtual const Cache& cache() const = 0; + // runtime returns an immutable Runtime instance. + virtual const Runtime& runtime() const = 0; +protected: + BaseDirSpecification() = default; +}; + +// data returns an immutable reference to a Data instance. +const Data& data(); +// config returns an immutable reference to a Config instance. +const Config& config(); +// cache returns an immutable reference to a Cache instance. +const Cache& cache(); +// runtime returns an immutable reference to a Runtime instance. +const Runtime& runtime(); +} + +#endif // XDG_H_ diff --git a/external/xdg/xdg_test.cpp b/external/xdg/xdg_test.cpp new file mode 100644 index 0000000..d5b3632 --- /dev/null +++ b/external/xdg/xdg_test.cpp @@ -0,0 +1,130 @@ +// Copyright (C) 2015 Thomas Voß +// +// This library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#include + +#include + +#include +#include + +BOOST_AUTO_TEST_CASE(XdgDataHomeThrowsForRelativeDirectoryFromEnv) +{ + ::setenv("XDG_DATA_HOME", "tmp", 1); + BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->data().home(), std::runtime_error); + BOOST_CHECK_THROW(xdg::data().home(), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(XdgDataHomeReturnsDefaultValueForEmptyEnv) +{ + ::setenv("HOME", "/tmp", 1); + ::setenv("XDG_DATA_HOME", "", 1); + BOOST_CHECK_EQUAL("/tmp/.local/share", xdg::BaseDirSpecification::create()->data().home()); + BOOST_CHECK_EQUAL("/tmp/.local/share", xdg::data().home()); +} + +BOOST_AUTO_TEST_CASE(XdgDataDirsCorrectlyTokenizesEnv) +{ + ::setenv("XDG_DATA_DIRS", "/tmp:/tmp", 1); + BOOST_CHECK(2 == xdg::BaseDirSpecification::create()->data().dirs().size()); + BOOST_CHECK(2 == xdg::data().dirs().size()); +} + +BOOST_AUTO_TEST_CASE(XdgDataDirsThrowsForRelativeDirectoryFromEnv) +{ + ::setenv("XDG_DATA_DIRS", "/tmp:tmp", 1); + BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->data().dirs(), std::runtime_error); + BOOST_CHECK_THROW(xdg::data().dirs(), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(XdgDataDirsReturnsDefaultValueForEmptyEnv) +{ + ::setenv("XDG_DATA_DIRS", "", 1); + auto dirs = xdg::data().dirs(); + BOOST_CHECK_EQUAL("/usr/local/share", dirs[0]); + BOOST_CHECK_EQUAL("/usr/share", dirs[1]); + + dirs = xdg::BaseDirSpecification::create()->data().dirs(); + BOOST_CHECK_EQUAL("/usr/local/share", dirs[0]); + BOOST_CHECK_EQUAL("/usr/share", dirs[1]); +} + +BOOST_AUTO_TEST_CASE(XdgConfigHomeThrowsForRelativeDirectoryFromEnv) +{ + ::setenv("XDG_CONFIG_HOME", "tmp", 1); + BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->config().home(), std::runtime_error); + BOOST_CHECK_THROW(xdg::config().home(), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(XdgConfigHomeReturnsDefaultValueForEmptyEnv) +{ + ::setenv("HOME", "/tmp", 1); + ::setenv("XDG_CONFIG_HOME", "", 1); + BOOST_CHECK_EQUAL("/tmp/.config", xdg::BaseDirSpecification::create()->config().home()); + BOOST_CHECK_EQUAL("/tmp/.config", xdg::config().home()); +} + +BOOST_AUTO_TEST_CASE(XdgConfigDirsCorrectlyTokenizesEnv) +{ + ::setenv("XDG_CONFIG_DIRS", "/tmp:/tmp", 1); + BOOST_CHECK(2 == xdg::BaseDirSpecification::create()->config().dirs().size()); + BOOST_CHECK(2 == xdg::config().dirs().size()); +} + +BOOST_AUTO_TEST_CASE(XdgConfigDirsThrowsForRelativeDirectoryFromEnv) +{ + ::setenv("XDG_CONFIG_DIRS", "/tmp:tmp", 1); + BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->config().dirs(), std::runtime_error); + BOOST_CHECK_THROW(xdg::config().dirs(), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(XdgConfigDirsReturnsDefaultValueForEmptyEnv) +{ + ::setenv("XDG_CONFIG_DIRS", "", 1); + auto dirs = xdg::config().dirs(); + BOOST_CHECK_EQUAL("/etc/xdg", dirs[0]); + dirs = xdg::BaseDirSpecification::create()->config().dirs(); + BOOST_CHECK_EQUAL("/etc/xdg", dirs[0]); +} + +BOOST_AUTO_TEST_CASE(XdgCacheHomeThrowsForRelativeDirectoryFromEnv) +{ + ::setenv("XDG_CACHE_HOME", "tmp", 1); + BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->cache().home(), std::runtime_error); + BOOST_CHECK_THROW(xdg::cache().home(), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(XdgCacheHomeReturnsDefaultValueForEmptyEnv) +{ + ::setenv("HOME", "/tmp", 1); + ::setenv("XDG_CACHE_HOME", "", 1); + BOOST_CHECK_EQUAL("/tmp/.cache", xdg::BaseDirSpecification::create()->cache().home()); + BOOST_CHECK_EQUAL("/tmp/.cache", xdg::cache().home()); +} + +BOOST_AUTO_TEST_CASE(XdgRuntimeDirThrowsForRelativeDirectoryFromEnv) +{ + ::setenv("XDG_RUNTIME_DIR", "tmp", 1); + BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->runtime().dir(), std::runtime_error); + BOOST_CHECK_THROW(xdg::runtime().dir(), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(XdgRuntimeDirThrowsForEmptyEnv) +{ + ::setenv("XDG_RUNTIME_DIR", "", 1); + BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->runtime().dir(), std::runtime_error); + BOOST_CHECK_THROW(xdg::runtime().dir(), std::runtime_error); +} + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84d5209..fb7c899 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,8 @@ set(SOURCES anbox/not_reachable.cpp anbox/application_manager.h + anbox/android/intent.cpp + anbox/common/fd.cpp anbox/common/fd_sets.h anbox/common/variable_length_array.h @@ -168,9 +170,10 @@ set(SOURCES anbox/dbus/skeleton/application_manager.cpp anbox/dbus/stub/application_manager.cpp + anbox/application/launcher_storage.cpp + anbox/cmds/version.cpp anbox/cmds/run.cpp - anbox/cmds/install.cpp anbox/cmds/launch.cpp anbox/cmds/container_manager.cpp @@ -193,7 +196,8 @@ target_link_libraries(anbox-core renderControl_dec OpenGLESDispatch OpenglCodecCommon - anbox-protobuf) + anbox-protobuf + xdg) add_executable(anbox main.cpp) target_link_libraries(anbox diff --git a/src/anbox/android/intent.cpp b/src/anbox/android/intent.cpp new file mode 100644 index 0000000..61c927b --- /dev/null +++ b/src/anbox/android/intent.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#include "anbox/android/intent.h" + +#include + +namespace anbox { +namespace android { +std::ostream& operator<<(std::ostream &out, const Intent &intent) +{ + out << "[" + << "action=" << intent.action << " " + << "uri=" << intent.uri << " " + << "type=" << intent.type << " " + << "flags=" << intent.flags << " " + << "package=" << intent.package << " " + << "component=" << intent.component << " " + << "categories=[ "; + for (const auto &category : intent.categories) + out << category << " "; + out << "]]"; + return out; +} +} // namespace android +} // namespace anbox + diff --git a/src/anbox/android/intent.h b/src/anbox/android/intent.h new file mode 100644 index 0000000..107dd05 --- /dev/null +++ b/src/anbox/android/intent.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#ifndef ANBOX_ANDROID_INTENT_H_ +#define ANBOX_ANDROID_INTENT_H_ + +#include +#include + +namespace anbox { +namespace android { +struct Intent { + std::string action; + std::string uri; + std::string type; + int flags = 0; + std::string package; + std::string component; + std::vector categories; +}; + +std::ostream& operator<<(std::ostream &out, const Intent &intent); +} // namespace android +} // namespace anbox + + +#endif diff --git a/src/anbox/application/launcher_storage.cpp b/src/anbox/application/launcher_storage.cpp new file mode 100644 index 0000000..871f709 --- /dev/null +++ b/src/anbox/application/launcher_storage.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#include "anbox/application/launcher_storage.h" +#include "anbox/utils.h" + +#include +#include +#include + +namespace fs = boost::filesystem; + +namespace anbox { +namespace application { +LauncherStorage::LauncherStorage(const fs::path &path) : + path_(path) { +} + +LauncherStorage::~LauncherStorage() { +} + +void LauncherStorage::add(const Item &item) { + if (!fs::exists(path_)) + fs::create_directories(path_); + + auto package_name = item.package; + std::replace(package_name.begin(), package_name.end(), '.', '-'); + + const auto item_path = path_ / utils::string_format("anbox-%s.desktop", package_name); + std::string exec = "anbox launch "; + + if (!item.launch_intent.action.empty()) + exec += utils::string_format("--action=%s ", item.launch_intent.action); + + if (!item.launch_intent.type.empty()) + exec += utils::string_format("--type=%s ", item.launch_intent.type); + + if (!item.launch_intent.uri.empty()) + exec += utils::string_format("--uri=%s ", item.launch_intent.uri); + + if (!item.launch_intent.package.empty()) + exec += utils::string_format("--package=%s ", item.launch_intent.package); + + if (!item.launch_intent.component.empty()) + exec += utils::string_format("--component=%s ", item.launch_intent.component); + + std::ofstream f(item_path.string()); + f << "[Desktop Entry]" << std::endl + << "Name=" << item.package << std::endl + << "Exec=" << exec << std::endl + << "Terminal=false" << std::endl + << "Type=Application" << std::endl + << "Encoding=UTF-8" << std::endl; + f.close(); +} +} // namespace application +} // namespace anbox diff --git a/src/anbox/application/launcher_storage.h b/src/anbox/application/launcher_storage.h new file mode 100644 index 0000000..c6c21eb --- /dev/null +++ b/src/anbox/application/launcher_storage.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#ifndef ANBOX_APPLICATION_LAUNCHER_STORAGE_H_ +#define ANBOX_APPLICATION_LAUNCHER_STORAGE_H_ + +#include "anbox/android/intent.h" + +#include +#include + +#include + +namespace anbox { +namespace application { +class LauncherStorage { +public: + LauncherStorage(const boost::filesystem::path &path); + ~LauncherStorage(); + + struct Item { + std::string name; + std::string package; + android::Intent launch_intent; + }; + + void add(const Item &item); + +private: + boost::filesystem::path path_; +}; +} // namespace application +} // namespace anbox + +#endif diff --git a/src/anbox/application_manager.h b/src/anbox/application_manager.h index 2d06b9c..52dea49 100644 --- a/src/anbox/application_manager.h +++ b/src/anbox/application_manager.h @@ -19,14 +19,14 @@ #define ANBOX_APPLICATION_MANAGER_H_ #include "anbox/do_not_copy_or_move.h" +#include "anbox/android/intent.h" #include namespace anbox { class ApplicationManager : public DoNotCopyOrMove { public: - virtual void install(const std::string &path) = 0; - virtual void launch(const std::string &package, const std::string &activity) = 0; + virtual void launch(const android::Intent &intent) = 0; }; } // namespace anbox diff --git a/src/anbox/bridge/android_api_stub.cpp b/src/anbox/bridge/android_api_stub.cpp index a4a9f46..a6f5b6f 100644 --- a/src/anbox/bridge/android_api_stub.cpp +++ b/src/anbox/bridge/android_api_stub.cpp @@ -49,57 +49,39 @@ void AndroidApiStub::ensure_rpc_channel() { throw std::runtime_error("No remote client connected"); } -void AndroidApiStub::install(const std::string &path) { - ensure_rpc_channel(); - - const auto target_path = utils::string_format("/data/anbox-share/%s", fs::path(path).filename().string()); - - if (fs::exists(target_path)) - fs::remove(target_path); - - fs::copy(path, target_path); - - const auto container_path = utils::string_format("/data/anbox-share/%s", fs::path(path).filename().string()); - - auto c = std::make_shared>(); - protobuf::bridge::InstallApplication message; - message.set_path(container_path); - - { - std::lock_guard lock(mutex_); - install_wait_handle_.expect_result(); - } - - channel_->call_method("install_application", - &message, - c->response.get(), - google::protobuf::NewCallback(this, &AndroidApiStub::application_installed, c.get())); - - install_wait_handle_.wait_for_all(); - - if (c->response->has_error()) - throw std::runtime_error(c->response->error()); -} - -void AndroidApiStub::application_installed(Request *request) { - (void) request; - install_wait_handle_.result_received(); -} - -void AndroidApiStub::launch(const std::string &package, const std::string &activity) { +void AndroidApiStub::launch(const android::Intent &intent) { ensure_rpc_channel(); auto c = std::make_shared>(); protobuf::bridge::LaunchApplication message; - message.set_package_name(package); - if (activity.length() > 0) - message.set_activity(activity); { std::lock_guard lock(mutex_); launch_wait_handle_.expect_result(); } + auto launch_intent = message.mutable_intent(); + + if (!intent.action.empty()) + launch_intent->set_action(intent.action); + + if (!intent.uri.empty()) + launch_intent->set_uri(intent.uri); + + if (!intent.type.empty()) + launch_intent->set_type(intent.type); + + if (!intent.package.empty()) + launch_intent->set_package(intent.package); + + if (!intent.component.empty()) + launch_intent->set_component(intent.component); + + for (const auto &category : intent.categories) { + auto c = launch_intent->add_categories(); + *c = category; + } + channel_->call_method("launch_application", &message, c->response.get(), @@ -116,40 +98,6 @@ void AndroidApiStub::application_launched(Request *request) launch_wait_handle_.result_received(); } -void AndroidApiStub::set_dns_servers(const std::string &domain, const std::vector &servers) { - ensure_rpc_channel(); - - auto c = std::make_shared>(); - - protobuf::bridge::SetDnsServers message; - message.set_domain(domain); - - for (const auto &server : servers) { - auto server_message = message.add_servers(); - server_message->set_address(server); - } - - { - std::lock_guard lock(mutex_); - set_dns_servers_wait_handle_.expect_result(); - } - - channel_->call_method("set_dns_servers", - &message, - c->response.get(), - google::protobuf::NewCallback(this, &AndroidApiStub::dns_servers_set, c.get())); - - set_dns_servers_wait_handle_.wait_for_all(); - - if (c->response->has_error()) - throw std::runtime_error(c->response->error()); -} - -void AndroidApiStub::dns_servers_set(Request *request) { - (void) request; - set_dns_servers_wait_handle_.result_received(); -} - void AndroidApiStub::set_focused_task(const std::int32_t &id) { ensure_rpc_channel(); diff --git a/src/anbox/bridge/android_api_stub.h b/src/anbox/bridge/android_api_stub.h index 73cff7c..9f8d1ac 100644 --- a/src/anbox/bridge/android_api_stub.h +++ b/src/anbox/bridge/android_api_stub.h @@ -42,10 +42,8 @@ public: void set_rpc_channel(const std::shared_ptr &channel); void reset_rpc_channel(); - void install(const std::string &path) override; - void launch(const std::string &package, const std::string &activity) override; + void launch(const android::Intent &intent) override; - void set_dns_servers(const std::string &domain, const std::vector &servers); void set_focused_task(const std::int32_t &id); private: @@ -58,16 +56,12 @@ private: bool success; }; - void application_installed(Request *request); void application_launched(Request *request); - void dns_servers_set(Request *request); void focused_task_set(Request *request); mutable std::mutex mutex_; std::shared_ptr channel_; - common::WaitHandle install_wait_handle_; common::WaitHandle launch_wait_handle_; - common::WaitHandle set_dns_servers_wait_handle_; common::WaitHandle set_focused_task_handle_; }; } // namespace bridge diff --git a/src/anbox/bridge/platform_api_skeleton.cpp b/src/anbox/bridge/platform_api_skeleton.cpp index cb21e67..02966f8 100644 --- a/src/anbox/bridge/platform_api_skeleton.cpp +++ b/src/anbox/bridge/platform_api_skeleton.cpp @@ -16,6 +16,7 @@ */ #include "anbox/bridge/platform_api_skeleton.h" +#include "anbox/application/launcher_storage.h" #include "anbox/wm/manager.h" #include "anbox/wm/window_state.h" #include "anbox/logger.h" @@ -25,9 +26,11 @@ namespace anbox { namespace bridge { PlatformApiSkeleton::PlatformApiSkeleton(const std::shared_ptr &pending_calls, - const std::shared_ptr &window_manager) : + const std::shared_ptr &window_manager, + const std::shared_ptr &launcher_storage) : pending_calls_(pending_calls), - window_manager_(window_manager) { + window_manager_(window_manager), + launcher_storage_(launcher_storage) { } PlatformApiSkeleton::~PlatformApiSkeleton() { @@ -66,6 +69,29 @@ void PlatformApiSkeleton::handle_window_state_update_event(const anbox::protobuf window_manager_->apply_window_state_update(updated, removed); } +void PlatformApiSkeleton::handle_application_list_update_event(const anbox::protobuf::bridge::ApplicationListUpdateEvent &event) { + for (int n = 0; n < event.applications_size(); n++) { + application::LauncherStorage::Item item; + + const auto app = event.applications(n); + item.name = app.name(); + item.package = app.package(); + + const auto li = app.launch_intent(); + item.launch_intent.action = li.action(); + item.launch_intent.uri = li.uri(); + item.launch_intent.type = li.uri(); + item.launch_intent.package = li.package(); + item.launch_intent.component = li.component(); + + for (int m = 0; m < li.categories_size(); m++) + item.launch_intent.categories.push_back(li.categories(m)); + + // If the item is already stored it will be updated + launcher_storage_->add(item); + } +} + void PlatformApiSkeleton::register_boot_finished_handler(const std::function &action) { boot_finished_handler_ = action; } diff --git a/src/anbox/bridge/platform_api_skeleton.h b/src/anbox/bridge/platform_api_skeleton.h index 030e7ea..0cf0c3b 100644 --- a/src/anbox/bridge/platform_api_skeleton.h +++ b/src/anbox/bridge/platform_api_skeleton.h @@ -34,6 +34,7 @@ class Void; namespace bridge { class BootFinishedEvent; class WindowStateUpdateEvent; +class ApplicationListUpdateEvent; } // namespace bridge } // namespace protobuf namespace rpc { @@ -42,21 +43,27 @@ class PendingCallCache; namespace wm { class Manager; } // namespace wm +namespace application { +class LauncherStorage; +} // namespace application namespace bridge { class PlatformApiSkeleton { public: PlatformApiSkeleton(const std::shared_ptr &pending_calls, - const std::shared_ptr &window_manager); + const std::shared_ptr &window_manager, + const std::shared_ptr &launcher_storage); virtual ~PlatformApiSkeleton(); void handle_boot_finished_event(const anbox::protobuf::bridge::BootFinishedEvent &event); void handle_window_state_update_event(const anbox::protobuf::bridge::WindowStateUpdateEvent &event); + void handle_application_list_update_event(const anbox::protobuf::bridge::ApplicationListUpdateEvent &event); void register_boot_finished_handler(const std::function &action); private: std::shared_ptr pending_calls_; std::shared_ptr window_manager_; + std::shared_ptr launcher_storage_; std::function boot_finished_handler_; }; } // namespace bridge diff --git a/src/anbox/bridge/platform_message_processor.cpp b/src/anbox/bridge/platform_message_processor.cpp index 5b9892c..4bffa79 100644 --- a/src/anbox/bridge/platform_message_processor.cpp +++ b/src/anbox/bridge/platform_message_processor.cpp @@ -44,11 +44,14 @@ void PlatformMessageProcessor::process_event_sequence(const std::string &raw_eve return; } + if (seq.has_boot_finished()) + server_->handle_boot_finished_event(seq.boot_finished()); + if (seq.has_window_state_update()) server_->handle_window_state_update_event(seq.window_state_update()); - if (seq.has_boot_finished()) - server_->handle_boot_finished_event(seq.boot_finished()); + if (seq.has_application_list_update()) + server_->handle_application_list_update_event(seq.application_list_update()); } } // namespace anbox } // namespace network diff --git a/src/anbox/cmds/launch.cpp b/src/anbox/cmds/launch.cpp index 809926e..b86da8a 100644 --- a/src/anbox/cmds/launch.cpp +++ b/src/anbox/cmds/launch.cpp @@ -25,19 +25,20 @@ namespace fs = boost::filesystem; anbox::cmds::Launch::Launch() - : CommandWithFlagsAndAction{cli::Name{"launch"}, cli::Usage{"launch"}, cli::Description{"Launch specified application in the Android container"}} + : CommandWithFlagsAndAction{cli::Name{"launch"}, cli::Usage{"launch"}, cli::Description{"Launch an Activity by sending an intent"}} { - flag(cli::make_flag(cli::Name{"package"}, cli::Description{"Package the application is part of"}, package_)); - flag(cli::make_flag(cli::Name{"activity"}, cli::Description{"Activity of the application to start"}, activity_)); - action([this](const cli::Command::Context&) { - if (package_.empty() && activity_.empty()) - BOOST_THROW_EXCEPTION(std::runtime_error("Package or activity name not specified")); + flag(cli::make_flag(cli::Name{"action"}, cli::Description{"Action of the intent"}, intent_.action)); + flag(cli::make_flag(cli::Name{"type"}, cli::Description{"MIME type for the intent"}, intent_.type)); + flag(cli::make_flag(cli::Name{"uri"}, cli::Description{"URI used as data within the intent"}, intent_.uri)); + flag(cli::make_flag(cli::Name{"package"}, cli::Description{"Package the intent should go to"}, intent_.package)); + flag(cli::make_flag(cli::Name{"component"}, cli::Description{"Component of a package the intent should go"}, intent_.component)); + action([this](const cli::Command::Context&) { auto bus = std::make_shared(core::dbus::WellKnownBus::session); bus->install_executor(core::dbus::asio::make_executor(bus)); auto stub = dbus::stub::ApplicationManager::create_for_bus(bus); - stub->launch(package_, activity_); + stub->launch(intent_); return EXIT_SUCCESS; }); diff --git a/src/anbox/cmds/launch.h b/src/anbox/cmds/launch.h index bbbbe54..98075a0 100644 --- a/src/anbox/cmds/launch.h +++ b/src/anbox/cmds/launch.h @@ -23,6 +23,7 @@ #include #include "anbox/cli.h" +#include "anbox/android/intent.h" namespace anbox { namespace cmds { @@ -31,8 +32,7 @@ public: Launch(); private: - std::string package_; - std::string activity_; + android::Intent intent_; }; } // namespace cmds } // namespace anbox diff --git a/src/anbox/cmds/run.cpp b/src/anbox/cmds/run.cpp index f32c522..84a4b66 100644 --- a/src/anbox/cmds/run.cpp +++ b/src/anbox/cmds/run.cpp @@ -37,6 +37,9 @@ #include "anbox/container/client.h" #include "anbox/wm/manager.h" #include "anbox/ubuntu/platform_policy.h" +#include "anbox/application/launcher_storage.h" + +#include "external/xdg/xdg.h" #include @@ -97,6 +100,9 @@ anbox::cmds::Run::Run(const BusFactory& bus_factory) auto window_manager = std::make_shared(policy); + auto launcher_storage = std::make_shared( + xdg::data().home() / "applications"); + auto renderer = std::make_shared(window_manager); renderer->start(); @@ -130,14 +136,11 @@ anbox::cmds::Run::Run(const BusFactory& bus_factory) // more than one one day we need proper dispatching to the right one. android_api_stub->set_rpc_channel(rpc_channel); - auto server = std::make_shared(pending_calls, window_manager); + auto server = std::make_shared(pending_calls, + window_manager, + launcher_storage); server->register_boot_finished_handler([&]() { DEBUG("Android successfully booted"); - dispatcher->dispatch([&]() { - // FIXME make this configurable or once we have a bridge let the host - // act as a DNS proxy. - android_api_stub->set_dns_servers("anbox", std::vector{ "8.8.8.8" }); - }); }); return std::make_shared(sender, server, pending_calls); })); diff --git a/src/anbox/daemon.cpp b/src/anbox/daemon.cpp index dd91e39..5953ab3 100644 --- a/src/anbox/daemon.cpp +++ b/src/anbox/daemon.cpp @@ -24,7 +24,6 @@ #include "anbox/cmds/version.h" #include "anbox/cmds/run.h" -#include "anbox/cmds/install.h" #include "anbox/cmds/launch.h" #include "anbox/cmds/container_manager.h" @@ -38,7 +37,6 @@ Daemon::Daemon() : cmd.command(std::make_shared()) .command(std::make_shared()) - .command(std::make_shared()) .command(std::make_shared()) .command(std::make_shared()); } diff --git a/src/anbox/dbus/interface.h b/src/anbox/dbus/interface.h index 43b1f43..bc9a202 100644 --- a/src/anbox/dbus/interface.h +++ b/src/anbox/dbus/interface.h @@ -33,12 +33,6 @@ struct Service { struct ApplicationManager { static inline std::string name() { return "org.anbox.ApplicationManager"; } struct Methods { - struct Install { - static inline std::string name() { return "Install"; } - typedef anbox::dbus::interface::ApplicationManager Interface; - typedef void ResultType; - static inline std::chrono::milliseconds default_timeout() { return std::chrono::seconds{240}; } - }; struct Launch { static inline std::string name() { return "Launch"; } typedef anbox::dbus::interface::ApplicationManager Interface; diff --git a/src/anbox/dbus/skeleton/application_manager.cpp b/src/anbox/dbus/skeleton/application_manager.cpp index 04f0d26..6db626b 100644 --- a/src/anbox/dbus/skeleton/application_manager.cpp +++ b/src/anbox/dbus/skeleton/application_manager.cpp @@ -17,6 +17,7 @@ #include "anbox/dbus/skeleton/application_manager.h" #include "anbox/dbus/interface.h" +#include "anbox/android/intent.h" #include "anbox/logger.h" namespace anbox { @@ -29,41 +30,22 @@ ApplicationManager::ApplicationManager(const core::dbus::Bus::Ptr &bus, object_(object), impl_(impl) { - object_->install_method_handler( - [this](const core::dbus::Message::Ptr &msg) { - std::string path; - auto reader = msg->reader(); - reader >> path; - - core::dbus::Message::Ptr reply; - - try { - install(path); - DEBUG("install done"); - reply = core::dbus::Message::make_method_return(msg); - DEBUG("Successfully installed application"); - } - catch (std::exception const &err) { - DEBUG("Failed to install application: %s", err.what()); - reply = core::dbus::Message::make_error(msg, - "org.anbox.Error.Failed", - err.what()); - } - - bus_->send(reply); - }); - object_->install_method_handler( [this](const core::dbus::Message::Ptr &msg) { - std::string package, activity; auto reader = msg->reader(); - reader >> package; - reader >> activity; + + android::Intent intent; + reader >> intent.action; + reader >> intent.uri; + reader >> intent.type; + reader >> intent.flags; + reader >> intent.package; + reader >> intent.component; core::dbus::Message::Ptr reply; try { - launch(package, activity); + launch(intent); reply = core::dbus::Message::make_method_return(msg); } catch (std::exception const &err) { @@ -77,15 +59,10 @@ ApplicationManager::ApplicationManager(const core::dbus::Bus::Ptr &bus, } ApplicationManager::~ApplicationManager() { - object_->uninstall_method_handler(); } -void ApplicationManager::install(const std::string &path) { - impl_->install(path); -} - -void ApplicationManager::launch(const std::string &package, const std::string &activity) { - impl_->launch(package, activity); +void ApplicationManager::launch(const android::Intent &intent) { + impl_->launch(intent); } } // namespace skeleton } // namespace dbus diff --git a/src/anbox/dbus/skeleton/application_manager.h b/src/anbox/dbus/skeleton/application_manager.h index 3ccba27..d894e1f 100644 --- a/src/anbox/dbus/skeleton/application_manager.h +++ b/src/anbox/dbus/skeleton/application_manager.h @@ -34,8 +34,7 @@ public: const std::shared_ptr &impl); ~ApplicationManager(); - void install(const std::string &path) override; - void launch(const std::string &package, const std::string &activity) override; + void launch(const android::Intent &intent) override; private: core::dbus::Bus::Ptr bus_; diff --git a/src/anbox/dbus/stub/application_manager.cpp b/src/anbox/dbus/stub/application_manager.cpp index e9f4bb7..3388a9d 100644 --- a/src/anbox/dbus/stub/application_manager.cpp +++ b/src/anbox/dbus/stub/application_manager.cpp @@ -39,23 +39,16 @@ ApplicationManager::ApplicationManager(const core::dbus::Bus::Ptr &bus, ApplicationManager::~ApplicationManager() { } -void ApplicationManager::install(const std::string &path) { - DEBUG("path %s", path); - - auto result = object_->invoke_method_synchronously< - anbox::dbus::interface::ApplicationManager::Methods::Install, - anbox::dbus::interface::ApplicationManager::Methods::Install::ResultType>(path); - - if (result.is_error()) - throw std::runtime_error(result.error().print()); -} - -void ApplicationManager::launch(const std::string &package, const std::string &activity) { - DEBUG("package %s activity %s", package, activity); - +void ApplicationManager::launch(const android::Intent &intent) { auto result = object_->invoke_method_synchronously< anbox::dbus::interface::ApplicationManager::Methods::Launch, - anbox::dbus::interface::ApplicationManager::Methods::Launch::ResultType>(package, activity); + anbox::dbus::interface::ApplicationManager::Methods::Launch::ResultType>( + intent.action, + intent.uri, + intent.type, + intent.flags, + intent.package, + intent.component); if (result.is_error()) throw std::runtime_error(result.error().print()); diff --git a/src/anbox/dbus/stub/application_manager.h b/src/anbox/dbus/stub/application_manager.h index f01ef16..b7ceca8 100644 --- a/src/anbox/dbus/stub/application_manager.h +++ b/src/anbox/dbus/stub/application_manager.h @@ -36,8 +36,7 @@ public: const core::dbus::Object::Ptr& object); ~ApplicationManager(); - void install(const std::string &path) override; - void launch(const std::string &package, const std::string &activity) override; + void launch(const android::Intent &intent) override; private: core::dbus::Bus::Ptr bus_; diff --git a/src/anbox/protobuf/anbox_bridge.proto b/src/anbox/protobuf/anbox_bridge.proto index 382a2b8..9c448ba 100644 --- a/src/anbox/protobuf/anbox_bridge.proto +++ b/src/anbox/protobuf/anbox_bridge.proto @@ -7,6 +7,15 @@ message StructuredError { optional uint32 code = 2; } +message Intent { + optional string action = 1; + optional string uri = 2; + optional string type = 3; + optional string package = 4; + optional string component = 5; + repeated string categories = 6; +} + message Notification { required string package_name = 1; required string category = 2; @@ -15,21 +24,8 @@ message Notification { optional string text = 5; } -message InstallApplication { - required string path = 1; -} - message LaunchApplication { - required string package_name = 1; - optional string activity = 2; -} - -message SetDnsServers { - required string domain = 1; - message Server { - required string address = 1; - } - repeated Server servers = 2; + required Intent intent = 1; } message SetFocusedTask { @@ -55,9 +51,19 @@ message WindowStateUpdateEvent { repeated WindowState removed_windows = 2; } +message ApplicationListUpdateEvent { + message Application { + required string name = 1; + required string package = 2; + optional Intent launch_intent = 3; + } + repeated Application applications = 1; +} + message EventSequence { optional BootFinishedEvent boot_finished = 1; optional WindowStateUpdateEvent window_state_update = 2; + optional ApplicationListUpdateEvent application_list_update = 3; optional string error = 127; optional StructuredError structured_error = 128;