| // Copyright 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "buffet/manager.h" |
| |
| #include <map> |
| #include <set> |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/json/json_reader.h> |
| #include <base/json/json_writer.h> |
| #include <base/message_loop/message_loop.h> |
| #include <base/time/time.h> |
| #include <binderwrapper/binder_wrapper.h> |
| #include <cutils/properties.h> |
| #include <brillo/bind_lambda.h> |
| #include <brillo/errors/error.h> |
| #include <brillo/http/http_transport.h> |
| #include <brillo/http/http_utils.h> |
| #include <brillo/key_value_store.h> |
| #include <brillo/message_loops/message_loop.h> |
| #include <brillo/mime_utils.h> |
| #include <dbus/bus.h> |
| #include <dbus/object_path.h> |
| #include <dbus/values_util.h> |
| #include <weave/enum_to_string.h> |
| |
| #include "brillo/weaved_system_properties.h" |
| #include "buffet/bluetooth_client.h" |
| #include "buffet/buffet_config.h" |
| #include "buffet/http_transport_client.h" |
| #include "buffet/mdns_client.h" |
| #include "buffet/shill_client.h" |
| #include "buffet/weave_error_conversion.h" |
| #include "buffet/webserv_client.h" |
| #include "common/binder_utils.h" |
| |
| using brillo::dbus_utils::AsyncEventSequencer; |
| using NotificationListener = |
| android::weave::IWeaveServiceManagerNotificationListener; |
| |
| namespace buffet { |
| |
| namespace { |
| |
| const char kErrorDomain[] = "buffet"; |
| const char kFileReadError[] = "file_read_error"; |
| const char kBaseComponent[] = "base"; |
| const char kRebootCommand[] = "base.reboot"; |
| |
| bool LoadFile(const base::FilePath& file_path, |
| std::string* data, |
| brillo::ErrorPtr* error) { |
| if (!base::ReadFileToString(file_path, data)) { |
| brillo::errors::system::AddSystemError(error, FROM_HERE, errno); |
| brillo::Error::AddToPrintf(error, FROM_HERE, kErrorDomain, kFileReadError, |
| "Failed to read file '%s'", |
| file_path.value().c_str()); |
| return false; |
| } |
| return true; |
| } |
| |
| void LoadTraitDefinitions(const BuffetConfig::Options& options, |
| weave::Device* device) { |
| // Load component-specific device trait definitions. |
| base::FilePath dir{options.definitions.Append("traits")}; |
| LOG(INFO) << "Looking for trait definitions in " << dir.value(); |
| base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.json")); |
| std::vector<std::string> result; |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| LOG(INFO) << "Loading trait definition from " << path.value(); |
| std::string json; |
| CHECK(LoadFile(path, &json, nullptr)); |
| device->AddTraitDefinitionsFromJson(json); |
| } |
| } |
| |
| void LoadCommandDefinitions(const BuffetConfig::Options& options, |
| weave::Device* device) { |
| auto load_packages = [device](const base::FilePath& root, |
| const base::FilePath::StringType& pattern) { |
| base::FilePath dir{root.Append("commands")}; |
| LOG(INFO) << "Looking for command schemas in " << dir.value(); |
| base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES, |
| pattern); |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| LOG(INFO) << "Loading command schema from " << path.value(); |
| std::string json; |
| CHECK(LoadFile(path, &json, nullptr)); |
| device->AddCommandDefinitionsFromJson(json); |
| } |
| }; |
| load_packages(options.definitions, FILE_PATH_LITERAL("*.json")); |
| if (!options.test_definitions.empty()) |
| load_packages(options.test_definitions, FILE_PATH_LITERAL("*test.json")); |
| } |
| |
| void LoadStateDefinitions(const BuffetConfig::Options& options, |
| weave::Device* device) { |
| // Load component-specific device state definitions. |
| base::FilePath dir{options.definitions.Append("states")}; |
| LOG(INFO) << "Looking for state definitions in " << dir.value(); |
| base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.schema.json")); |
| std::vector<std::string> result; |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| LOG(INFO) << "Loading state definition from " << path.value(); |
| std::string json; |
| CHECK(LoadFile(path, &json, nullptr)); |
| device->AddStateDefinitionsFromJson(json); |
| } |
| } |
| |
| void LoadStateDefaults(const BuffetConfig::Options& options, |
| weave::Device* device) { |
| // Load component-specific device state defaults. |
| base::FilePath dir{options.definitions.Append("states")}; |
| LOG(INFO) << "Looking for state defaults in " << dir.value(); |
| base::FileEnumerator enumerator(dir, false, base::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.defaults.json")); |
| std::vector<std::string> result; |
| for (base::FilePath path = enumerator.Next(); !path.empty(); |
| path = enumerator.Next()) { |
| LOG(INFO) << "Loading state defaults from " << path.value(); |
| std::string json; |
| CHECK(LoadFile(path, &json, nullptr)); |
| CHECK(device->SetStatePropertiesFromJson(json, nullptr)); |
| } |
| } |
| |
| // Updates the manager's state property if the new value is different from |
| // the current value. In this case also adds the appropriate notification ID |
| // to the array to record the state change for clients. |
| void UpdateValue(Manager* manager, |
| std::string Manager::* prop, |
| const std::string& new_value, |
| int notification, |
| std::vector<int>* notification_ids) { |
| if (manager->*prop != new_value) { |
| manager->*prop = new_value; |
| notification_ids->push_back(notification); |
| } |
| } |
| |
| } // anonymous namespace |
| |
| class Manager::TaskRunner : public weave::provider::TaskRunner { |
| public: |
| void PostDelayedTask(const tracked_objects::Location& from_here, |
| const base::Closure& task, |
| base::TimeDelta delay) override { |
| brillo::MessageLoop::current()->PostDelayedTask(from_here, task, delay); |
| } |
| }; |
| |
| Manager::Manager(const Options& options, |
| const scoped_refptr<dbus::Bus>& bus) |
| : options_{options}, bus_{bus} {} |
| |
| Manager::~Manager() { |
| android::BinderWrapper* binder_wrapper = android::BinderWrapper::Get(); |
| for (const auto& listener : notification_listeners_) { |
| binder_wrapper->UnregisterForDeathNotifications( |
| android::IInterface::asBinder(listener)); |
| } |
| for (const auto& pair : services_) { |
| binder_wrapper->UnregisterForDeathNotifications( |
| android::IInterface::asBinder(pair.first)); |
| } |
| } |
| |
| void Manager::Start(AsyncEventSequencer* sequencer) { |
| power_manager_client_.Init(); |
| RestartWeave(sequencer); |
| } |
| |
| void Manager::RestartWeave(AsyncEventSequencer* sequencer) { |
| Stop(); |
| |
| task_runner_.reset(new TaskRunner{}); |
| config_.reset(new BuffetConfig{options_.config_options}); |
| http_client_.reset(new HttpTransportClient); |
| shill_client_.reset(new ShillClient{bus_, |
| options_.device_whitelist, |
| !options_.xmpp_enabled}); |
| weave::provider::HttpServer* http_server{nullptr}; |
| #ifdef BUFFET_USE_WIFI_BOOTSTRAPPING |
| if (!options_.disable_privet) { |
| mdns_client_ = MdnsClient::CreateInstance(); |
| web_serv_client_.reset(new WebServClient{ |
| bus_, sequencer, |
| base::Bind(&Manager::CreateDevice, weak_ptr_factory_.GetWeakPtr())}); |
| bluetooth_client_ = BluetoothClient::CreateInstance(); |
| http_server = web_serv_client_.get(); |
| |
| if (options_.enable_ping) { |
| auto ping_handler = base::Bind( |
| [](std::unique_ptr<weave::provider::HttpServer::Request> request) { |
| request->SendReply(brillo::http::status_code::Ok, "Hello, world!", |
| brillo::mime::text::kPlain); |
| }); |
| http_server->AddHttpRequestHandler("/privet/ping", ping_handler); |
| http_server->AddHttpsRequestHandler("/privet/ping", ping_handler); |
| } |
| } |
| #endif // BUFFET_USE_WIFI_BOOTSTRAPPING |
| |
| if (!http_server) |
| CreateDevice(); |
| } |
| |
| void Manager::CreateDevice() { |
| if (device_) |
| return; |
| |
| device_ = weave::Device::Create(config_.get(), task_runner_.get(), |
| http_client_.get(), shill_client_.get(), |
| mdns_client_.get(), web_serv_client_.get(), |
| shill_client_.get(), bluetooth_client_.get()); |
| |
| LoadTraitDefinitions(options_.config_options, device_.get()); |
| LoadCommandDefinitions(options_.config_options, device_.get()); |
| LoadStateDefinitions(options_.config_options, device_.get()); |
| LoadStateDefaults(options_.config_options, device_.get()); |
| |
| device_->AddSettingsChangedCallback( |
| base::Bind(&Manager::OnConfigChanged, weak_ptr_factory_.GetWeakPtr())); |
| |
| device_->AddTraitDefsChangedCallback( |
| base::Bind(&Manager::OnTraitDefsChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| device_->AddStateChangedCallback( |
| base::Bind(&Manager::OnComponentTreeChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| device_->AddComponentTreeChangedCallback( |
| base::Bind(&Manager::OnComponentTreeChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| device_->AddGcdStateChangedCallback( |
| base::Bind(&Manager::OnGcdStateChanged, weak_ptr_factory_.GetWeakPtr())); |
| |
| device_->AddPairingChangedCallbacks( |
| base::Bind(&Manager::OnPairingStart, weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&Manager::OnPairingEnd, weak_ptr_factory_.GetWeakPtr())); |
| |
| device_->AddCommandHandler(kBaseComponent, kRebootCommand, |
| base::Bind(&Manager::OnRebootDevice, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| CreateServicesForClients(); |
| } |
| |
| void Manager::Stop() { |
| device_.reset(); |
| #ifdef BUFFET_USE_WIFI_BOOTSTRAPPING |
| web_serv_client_.reset(); |
| mdns_client_.reset(); |
| #endif // BUFFET_USE_WIFI_BOOTSTRAPPING |
| shill_client_.reset(); |
| http_client_.reset(); |
| config_.reset(); |
| task_runner_.reset(); |
| } |
| |
| void Manager::OnTraitDefsChanged() { |
| NotifyServiceManagerChange({NotificationListener::TRAITS}); |
| } |
| |
| void Manager::OnComponentTreeChanged() { |
| NotifyServiceManagerChange({NotificationListener::COMPONENTS}); |
| } |
| |
| void Manager::OnGcdStateChanged(weave::GcdState state) { |
| state_ = weave::EnumToString(state); |
| NotifyServiceManagerChange({NotificationListener::STATE}); |
| property_set(weaved::system_properties::kState, state_.c_str()); |
| } |
| |
| void Manager::OnConfigChanged(const weave::Settings& settings) { |
| std::vector<int> ids; |
| UpdateValue(this, &Manager::cloud_id_, settings.cloud_id, |
| NotificationListener::CLOUD_ID, &ids); |
| UpdateValue(this, &Manager::device_id_, settings.device_id, |
| NotificationListener::DEVICE_ID, &ids); |
| UpdateValue(this, &Manager::device_name_, settings.name, |
| NotificationListener::DEVICE_NAME, &ids); |
| UpdateValue(this, &Manager::device_description_, settings.description, |
| NotificationListener::DEVICE_DESCRIPTION, &ids); |
| UpdateValue(this, &Manager::device_location_, settings.location, |
| NotificationListener::DEVICE_LOCATION, &ids); |
| UpdateValue(this, &Manager::oem_name_, settings.oem_name, |
| NotificationListener::OEM_NAME, &ids); |
| UpdateValue(this, &Manager::model_id_, settings.model_id, |
| NotificationListener::MODEL_ID, &ids); |
| UpdateValue(this, &Manager::model_name_, settings.model_name, |
| NotificationListener::MODEL_NAME, &ids); |
| NotifyServiceManagerChange(ids); |
| } |
| |
| void Manager::OnPairingStart(const std::string& session_id, |
| weave::PairingType pairing_type, |
| const std::vector<uint8_t>& code) { |
| // For now, just overwrite the exposed PairInfo with the most recent pairing |
| // attempt. |
| std::vector<int> ids; |
| UpdateValue(this, &Manager::pairing_session_id_, session_id, |
| NotificationListener::PAIRING_SESSION_ID, &ids); |
| UpdateValue(this, &Manager::pairing_mode_, EnumToString(pairing_type), |
| NotificationListener::PAIRING_MODE, &ids); |
| std::string pairing_code{code.begin(), code.end()}; |
| UpdateValue(this, &Manager::pairing_code_, pairing_code, |
| NotificationListener::PAIRING_CODE, &ids); |
| NotifyServiceManagerChange(ids); |
| } |
| |
| void Manager::OnPairingEnd(const std::string& session_id) { |
| if (pairing_session_id_ != session_id) |
| return; |
| std::vector<int> ids; |
| UpdateValue(this, &Manager::pairing_session_id_, "", |
| NotificationListener::PAIRING_SESSION_ID, &ids); |
| UpdateValue(this, &Manager::pairing_mode_, "", |
| NotificationListener::PAIRING_MODE, &ids); |
| UpdateValue(this, &Manager::pairing_code_, "", |
| NotificationListener::PAIRING_CODE, &ids); |
| NotifyServiceManagerChange(ids); |
| } |
| |
| void Manager::OnRebootDevice(const std::weak_ptr<weave::Command>& cmd) { |
| auto command = cmd.lock(); |
| if (!command || !command->Complete({}, nullptr)) |
| return; |
| |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&Manager::RebootDeviceNow, weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromSeconds(2)); |
| } |
| |
| void Manager::RebootDeviceNow() { |
| power_manager_client_.Reboot(android::RebootReason::DEFAULT); |
| } |
| |
| android::binder::Status Manager::connect( |
| const android::sp<android::weave::IWeaveClient>& client) { |
| pending_clients_.push_back(client); |
| if (device_) |
| CreateServicesForClients(); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::registerNotificationListener( |
| const WeaveServiceManagerNotificationListener& listener) { |
| notification_listeners_.insert(listener); |
| android::BinderWrapper::Get()->RegisterForDeathNotifications( |
| android::IInterface::asBinder(listener), |
| base::Bind(&Manager::OnNotificationListenerDestroyed, |
| weak_ptr_factory_.GetWeakPtr(), listener)); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getCloudId(android::String16* id) { |
| *id = weaved::binder_utils::ToString16(cloud_id_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getDeviceId(android::String16* id) { |
| *id = weaved::binder_utils::ToString16(device_id_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getDeviceName(android::String16* name) { |
| *name = weaved::binder_utils::ToString16(device_name_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getDeviceDescription( |
| android::String16* description) { |
| *description = weaved::binder_utils::ToString16(device_description_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getDeviceLocation( |
| android::String16* location) { |
| *location = weaved::binder_utils::ToString16(device_location_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getOemName(android::String16* name) { |
| *name = weaved::binder_utils::ToString16(oem_name_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getModelName(android::String16* name) { |
| *name = weaved::binder_utils::ToString16(model_name_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getModelId(android::String16* id) { |
| *id = weaved::binder_utils::ToString16(model_id_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getPairingSessionId(android::String16* id) { |
| *id = weaved::binder_utils::ToString16(pairing_session_id_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getPairingMode(android::String16* mode) { |
| *mode = weaved::binder_utils::ToString16(pairing_mode_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getPairingCode(android::String16* code) { |
| *code = weaved::binder_utils::ToString16(pairing_code_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getState(android::String16* state) { |
| *state = weaved::binder_utils::ToString16(state_); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getTraits(android::String16* traits) { |
| *traits = weaved::binder_utils::ToString16(device_->GetTraits()); |
| return android::binder::Status::ok(); |
| } |
| |
| android::binder::Status Manager::getComponents(android::String16* components) { |
| *components = weaved::binder_utils::ToString16(device_->GetComponents()); |
| return android::binder::Status::ok(); |
| } |
| |
| void Manager::CreateServicesForClients() { |
| CHECK(device_); |
| // For safety, iterate over a copy of |pending_clients_| and clear the |
| // original vector before performing the iterations. |
| std::vector<android::sp<android::weave::IWeaveClient>> pending_clients_copy; |
| std::swap(pending_clients_copy, pending_clients_); |
| for (const auto& client : pending_clients_copy) { |
| android::sp<BinderWeaveService> service = |
| new BinderWeaveService{device_.get(), client}; |
| services_.emplace(client, service); |
| client->onServiceConnected(service); |
| android::BinderWrapper::Get()->RegisterForDeathNotifications( |
| android::IInterface::asBinder(client), |
| base::Bind(&Manager::OnClientDisconnected, |
| weak_ptr_factory_.GetWeakPtr(), |
| client)); |
| } |
| } |
| |
| void Manager::OnClientDisconnected( |
| const android::sp<android::weave::IWeaveClient>& client) { |
| services_.erase(client); |
| } |
| |
| void Manager::OnNotificationListenerDestroyed( |
| const WeaveServiceManagerNotificationListener& notification_listener) { |
| notification_listeners_.erase(notification_listener); |
| } |
| |
| void Manager::NotifyServiceManagerChange( |
| const std::vector<int>& notification_ids) { |
| if (notification_ids.empty()) |
| return; |
| for (const auto& listener : notification_listeners_) |
| listener->notifyServiceManagerChange(notification_ids); |
| } |
| |
| } // namespace buffet |