Merge pull request #52 from morphis/f/app-launcher-sync
Improve synchronization of application launchers between the host and Android
This commit is contained in:
commit
7288bb026e
10 changed files with 151 additions and 25 deletions
|
|
@ -20,12 +20,14 @@ package org.anbox.appmgr;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
|
||||||
public final class LauncherService extends Service {
|
public final class LauncherService extends Service {
|
||||||
public static final String TAG = "AnboxAppMgr";
|
public static final String TAG = "AnboxAppMgr";
|
||||||
|
|
||||||
private PlatformService mPlatformService;
|
private PlatformService mPlatformService;
|
||||||
|
private PackageEventReceiver mPkgEventReceiver;
|
||||||
|
|
||||||
public LauncherService() {
|
public LauncherService() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -35,9 +37,20 @@ public final class LauncherService extends Service {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
mPlatformService = new PlatformService(getBaseContext());
|
mPlatformService = new PlatformService(getBaseContext());
|
||||||
// Send all necessary initial updates
|
|
||||||
|
// Send the current list of applications over to the host so
|
||||||
|
// it can rebuild its list of available applications.
|
||||||
mPlatformService.sendApplicationListUpdate();
|
mPlatformService.sendApplicationListUpdate();
|
||||||
|
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
|
||||||
|
mPkgEventReceiver = new PackageEventReceiver();
|
||||||
|
registerReceiver(mPkgEventReceiver, filter);
|
||||||
|
|
||||||
Log.i(TAG, "Service started");
|
Log.i(TAG, "Service started");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,35 @@ package org.anbox.appmgr;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
public class PackageEventReceiver extends BroadcastReceiver {
|
public class PackageEventReceiver extends BroadcastReceiver {
|
||||||
private static final String TAG = "AnboxAppMgr";
|
private static final String TAG = "AnboxAppMgr";
|
||||||
|
|
||||||
|
private PlatformService mPlatformService;
|
||||||
|
|
||||||
|
private String getPackageName(Intent intent) {
|
||||||
|
Uri uri = intent.getData();
|
||||||
|
String package_name = (uri != null ? uri.getSchemeSpecificPart() : null);
|
||||||
|
return package_name;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
Log.i(TAG, "Received intent " + intent.toString());
|
if (mPlatformService == null)
|
||||||
|
mPlatformService = new PlatformService(context);
|
||||||
|
|
||||||
|
if (intent.getAction() == Intent.ACTION_PACKAGE_ADDED ||
|
||||||
|
intent.getAction() == Intent.ACTION_PACKAGE_CHANGED) {
|
||||||
|
// Send updated list of applications to the host so that it
|
||||||
|
// can update the list of applications available for the user.
|
||||||
|
mPlatformService.sendApplicationListUpdate();
|
||||||
|
} else if (intent.getAction() == Intent.ACTION_PACKAGE_REMOVED) {
|
||||||
|
// Only send notification when package got removed and not replaced
|
||||||
|
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
||||||
|
mPlatformService.notifyPackageRemoved(getPackageName(intent));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,31 @@ public final class PlatformService {
|
||||||
Log.i(TAG, "Connected to platform service");
|
Log.i(TAG, "Connected to platform service");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void notifyPackageRemoved(String packageName) {
|
||||||
|
connectService();
|
||||||
|
|
||||||
|
if (mService == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.i(TAG, "Sending package removed notification to host service");
|
||||||
|
|
||||||
|
Parcel data = Parcel.obtain();
|
||||||
|
data.writeInterfaceToken(DESCRIPTOR);
|
||||||
|
// No added or updated applications to report
|
||||||
|
data.writeInt(0);
|
||||||
|
// .. but a single removed application
|
||||||
|
data.writeInt(1);
|
||||||
|
data.writeString(packageName);
|
||||||
|
|
||||||
|
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 sendApplicationListUpdate() {
|
public void sendApplicationListUpdate() {
|
||||||
connectService();
|
connectService();
|
||||||
|
|
||||||
|
|
@ -112,6 +137,9 @@ public final class PlatformService {
|
||||||
data.writeByteArray(outStream.toByteArray());
|
data.writeByteArray(outStream.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't have any removed applications to include in the update
|
||||||
|
data.writeInt(0);
|
||||||
|
|
||||||
Parcel reply = Parcel.obtain();
|
Parcel reply = Parcel.obtain();
|
||||||
try {
|
try {
|
||||||
mService.transact(TRANSACTION_updateApplicationList, data, reply, 0);
|
mService.transact(TRANSACTION_updateApplicationList, data, reply, 0);
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,17 @@
|
||||||
#include "anbox_rpc.pb.h"
|
#include "anbox_rpc.pb.h"
|
||||||
#include "anbox_bridge.pb.h"
|
#include "anbox_bridge.pb.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#define LOG_TAG "Anboxd"
|
#define LOG_TAG "Anboxd"
|
||||||
#include <cutils/log.h>
|
#include <cutils/log.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr const char *first_boot_marker_path{"/data/.anbox_initialized"};
|
||||||
|
}
|
||||||
|
|
||||||
namespace anbox {
|
namespace anbox {
|
||||||
PlatformApiStub::PlatformApiStub(const std::shared_ptr<rpc::Channel> &rpc_channel) :
|
PlatformApiStub::PlatformApiStub(const std::shared_ptr<rpc::Channel> &rpc_channel) :
|
||||||
rpc_channel_(rpc_channel) {
|
rpc_channel_(rpc_channel) {
|
||||||
|
|
@ -32,7 +40,13 @@ PlatformApiStub::PlatformApiStub(const std::shared_ptr<rpc::Channel> &rpc_channe
|
||||||
void PlatformApiStub::boot_finished() {
|
void PlatformApiStub::boot_finished() {
|
||||||
protobuf::bridge::EventSequence seq;
|
protobuf::bridge::EventSequence seq;
|
||||||
auto event = seq.mutable_boot_finished();
|
auto event = seq.mutable_boot_finished();
|
||||||
(void) event;
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(first_boot_marker_path, &st) != 0) {
|
||||||
|
event->set_first_boot_done(true);
|
||||||
|
std::ofstream marker(first_boot_marker_path);
|
||||||
|
}
|
||||||
|
|
||||||
rpc_channel_->send_event(seq);
|
rpc_channel_->send_event(seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,6 +102,12 @@ void PlatformApiStub::update_application_list(const ApplicationListUpdate &updat
|
||||||
app->set_icon(a.icon.data(), a.icon.size());
|
app->set_icon(a.icon.data(), a.icon.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto &package : update.removed_applications) {
|
||||||
|
auto app = event->add_removed_applications();
|
||||||
|
app->set_name("unknown");
|
||||||
|
app->set_package(package);
|
||||||
|
}
|
||||||
|
|
||||||
rpc_channel_->send_event(seq);
|
rpc_channel_->send_event(seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ public:
|
||||||
std::vector<int8_t> icon;
|
std::vector<int8_t> icon;
|
||||||
};
|
};
|
||||||
std::vector<Application> applications;
|
std::vector<Application> applications;
|
||||||
|
std::vector<std::string> removed_applications;
|
||||||
};
|
};
|
||||||
|
|
||||||
void update_application_list(const ApplicationListUpdate &update);
|
void update_application_list(const ApplicationListUpdate &update);
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,12 @@ status_t PlatformService::update_application_list(const Parcel &data) {
|
||||||
update.applications.push_back(p);
|
update.applications.push_back(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto num_removed_packages = data.readInt32();
|
||||||
|
for (auto n = 0; n < num_removed_packages; n++) {
|
||||||
|
String8 package_name(data.readString16());
|
||||||
|
update.removed_applications.push_back(package_name.string());
|
||||||
|
}
|
||||||
|
|
||||||
platform_api_stub_->update_application_list(update);
|
platform_api_stub_->update_application_list(update);
|
||||||
|
|
||||||
return OK;
|
return OK;
|
||||||
|
|
|
||||||
|
|
@ -41,16 +41,27 @@ void LauncherStorage::reset() {
|
||||||
fs::remove_all(icon_path_);
|
fs::remove_all(icon_path_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LauncherStorage::add(const Item &item) {
|
std::string LauncherStorage::clean_package_name(const std::string &package_name) {
|
||||||
|
auto cleaned_package_name = package_name;
|
||||||
|
std::replace(cleaned_package_name.begin(), cleaned_package_name.end(), '.', '-');
|
||||||
|
return cleaned_package_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path LauncherStorage::path_for_item(const std::string &package_name) {
|
||||||
|
return path_ / utils::string_format("anbox-%s.desktop", package_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path LauncherStorage::path_for_item_icon(const std::string &package_name) {
|
||||||
|
return icon_path_ / utils::string_format("anbox-%s.png", package_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherStorage::add_or_update(const Item &item) {
|
||||||
if (!fs::exists(path_)) fs::create_directories(path_);
|
if (!fs::exists(path_)) fs::create_directories(path_);
|
||||||
if (!fs::exists(icon_path_)) fs::create_directories(icon_path_);
|
if (!fs::exists(icon_path_)) fs::create_directories(icon_path_);
|
||||||
|
|
||||||
auto package_name = item.package;
|
auto package_name = item.package;
|
||||||
std::replace(package_name.begin(), package_name.end(), '.', '-');
|
std::replace(package_name.begin(), package_name.end(), '.', '-');
|
||||||
|
|
||||||
const auto item_path = path_ / utils::string_format("anbox-%s.desktop", package_name);
|
|
||||||
const auto item_icon_path = icon_path_ / utils::string_format("anbox-%s.png", package_name);
|
|
||||||
|
|
||||||
std::string exec = utils::string_format("%s launch ", utils::process_get_exe_path(getpid()));
|
std::string exec = utils::string_format("%s launch ", utils::process_get_exe_path(getpid()));
|
||||||
|
|
||||||
if (!item.launch_intent.action.empty())
|
if (!item.launch_intent.action.empty())
|
||||||
|
|
@ -68,7 +79,8 @@ void LauncherStorage::add(const Item &item) {
|
||||||
if (!item.launch_intent.component.empty())
|
if (!item.launch_intent.component.empty())
|
||||||
exec += utils::string_format("--component=%s ", item.launch_intent.component);
|
exec += utils::string_format("--component=%s ", item.launch_intent.component);
|
||||||
|
|
||||||
if (auto desktop_item = std::ofstream(item_path.string())) {
|
const auto item_icon_path = path_for_item_icon(package_name);
|
||||||
|
if (auto desktop_item = std::ofstream(path_for_item(package_name).string())) {
|
||||||
desktop_item << "[Desktop Entry]" << std::endl
|
desktop_item << "[Desktop Entry]" << std::endl
|
||||||
<< "Name=" << item.package << std::endl
|
<< "Name=" << item.package << std::endl
|
||||||
<< "Exec=" << exec << std::endl
|
<< "Exec=" << exec << std::endl
|
||||||
|
|
@ -84,5 +96,18 @@ void LauncherStorage::add(const Item &item) {
|
||||||
else
|
else
|
||||||
BOOST_THROW_EXCEPTION(std::runtime_error("Failed to write icon"));
|
BOOST_THROW_EXCEPTION(std::runtime_error("Failed to write icon"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LauncherStorage::remove(const Item &item) {
|
||||||
|
auto package_name = clean_package_name(item.package);
|
||||||
|
|
||||||
|
const auto item_path = path_for_item(package_name);
|
||||||
|
if (fs::exists(item_path))
|
||||||
|
fs::remove(item_path);
|
||||||
|
|
||||||
|
const auto item_icon_path = path_for_item_icon(package_name);
|
||||||
|
if (fs::exists(item_icon_path))
|
||||||
|
fs::remove(item_icon_path);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace application
|
} // namespace application
|
||||||
} // namespace anbox
|
} // namespace anbox
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,14 @@ class LauncherStorage {
|
||||||
};
|
};
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void add(const Item &item);
|
void add_or_update(const Item &item);
|
||||||
|
void remove(const Item &item);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string clean_package_name(const std::string &package_name);
|
||||||
|
boost::filesystem::path path_for_item(const std::string &package_name);
|
||||||
|
boost::filesystem::path path_for_item_icon(const std::string &package_name);
|
||||||
|
|
||||||
boost::filesystem::path path_;
|
boost::filesystem::path path_;
|
||||||
boost::filesystem::path icon_path_;
|
boost::filesystem::path icon_path_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -62,15 +62,15 @@ void PlatformApiSkeleton::get_clipboard_data(anbox::protobuf::rpc::Void const *r
|
||||||
done->Run();
|
done->Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformApiSkeleton::handle_boot_finished_event(
|
void PlatformApiSkeleton::handle_boot_finished_event(const anbox::protobuf::bridge::BootFinishedEvent &event) {
|
||||||
const anbox::protobuf::bridge::BootFinishedEvent &event) {
|
if (event.first_boot_done())
|
||||||
(void)event;
|
launcher_storage_->reset();
|
||||||
|
|
||||||
if (boot_finished_handler_) boot_finished_handler_();
|
if (boot_finished_handler_)
|
||||||
|
boot_finished_handler_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformApiSkeleton::handle_window_state_update_event(
|
void PlatformApiSkeleton::handle_window_state_update_event(const anbox::protobuf::bridge::WindowStateUpdateEvent &event) {
|
||||||
const anbox::protobuf::bridge::WindowStateUpdateEvent &event) {
|
|
||||||
auto convert_window_state = [](
|
auto convert_window_state = [](
|
||||||
const ::anbox::protobuf::bridge::WindowStateUpdateEvent_WindowState
|
const ::anbox::protobuf::bridge::WindowStateUpdateEvent_WindowState
|
||||||
&window) {
|
&window) {
|
||||||
|
|
@ -97,12 +97,18 @@ void PlatformApiSkeleton::handle_window_state_update_event(
|
||||||
window_manager_->apply_window_state_update(updated, removed);
|
window_manager_->apply_window_state_update(updated, removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformApiSkeleton::handle_application_list_update_event(
|
void PlatformApiSkeleton::handle_application_list_update_event(const anbox::protobuf::bridge::ApplicationListUpdateEvent &event) {
|
||||||
const anbox::protobuf::bridge::ApplicationListUpdateEvent &event) {
|
for (int n = 0; n < event.removed_applications_size(); n++) {
|
||||||
// Remove all existing items to start from scratch for all
|
application::LauncherStorage::Item item;
|
||||||
// applications. We may need to improve this in the future
|
|
||||||
// to guarantee correct integration into desktop environments
|
const auto app = event.removed_applications(n);
|
||||||
launcher_storage_->reset();
|
item.package = app.package();
|
||||||
|
|
||||||
|
if (item.package.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
launcher_storage_->remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
for (int n = 0; n < event.applications_size(); n++) {
|
for (int n = 0; n < event.applications_size(); n++) {
|
||||||
application::LauncherStorage::Item item;
|
application::LauncherStorage::Item item;
|
||||||
|
|
@ -126,13 +132,11 @@ void PlatformApiSkeleton::handle_application_list_update_event(
|
||||||
if (item.package.empty())
|
if (item.package.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// If the item is already stored it will be updated
|
launcher_storage_->add_or_update(item);
|
||||||
launcher_storage_->add(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformApiSkeleton::register_boot_finished_handler(
|
void PlatformApiSkeleton::register_boot_finished_handler(const std::function<void()> &action) {
|
||||||
const std::function<void()> &action) {
|
|
||||||
boot_finished_handler_ = action;
|
boot_finished_handler_ = action;
|
||||||
}
|
}
|
||||||
} // namespace bridge
|
} // namespace bridge
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ message ClipboardData {
|
||||||
}
|
}
|
||||||
|
|
||||||
message BootFinishedEvent {
|
message BootFinishedEvent {
|
||||||
|
optional bool first_boot_done = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message WindowStateUpdateEvent {
|
message WindowStateUpdateEvent {
|
||||||
|
|
@ -84,6 +85,7 @@ message ApplicationListUpdateEvent {
|
||||||
optional bytes icon = 4;
|
optional bytes icon = 4;
|
||||||
}
|
}
|
||||||
repeated Application applications = 1;
|
repeated Application applications = 1;
|
||||||
|
repeated Application removed_applications = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EventSequence {
|
message EventSequence {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue