Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 89 additions & 157 deletions packages/battery_plus/tizen/src/battery_plus_tizen_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,199 +4,131 @@

#include "battery_plus_tizen_plugin.h"

#include <device/battery.h>
#include <device/callback.h>
#include <flutter/event_channel.h>
#include <flutter/event_sink.h>
#include <flutter/event_stream_handler_functions.h>
#include <flutter/event_stream_handler.h>
#include <flutter/method_channel.h>
#include <flutter/plugin_registrar.h>
#include <flutter/standard_method_codec.h>

#include <map>
#include <memory>
#include <string>

#include "log.h"

class BatteryPlusTizenPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrar *registrar) {
LOG_DEBUG("RegisterWithRegistrar for BatteryPlusTizenPlugin");
auto plugin = std::make_unique<BatteryPlusTizenPlugin>();
plugin->SetupChannels(registrar);
registrar->AddPlugin(std::move(plugin));
#include "device_battery.h"

namespace {

typedef flutter::EventChannel<flutter::EncodableValue> FlEventChannel;
typedef flutter::EventSink<flutter::EncodableValue> FlEventSink;
typedef flutter::MethodCall<flutter::EncodableValue> FlMethodCall;
typedef flutter::MethodResult<flutter::EncodableValue> FlMethodResult;
typedef flutter::MethodChannel<flutter::EncodableValue> FlMethodChannel;
typedef flutter::StreamHandler<flutter::EncodableValue> FlStreamHandler;
typedef flutter::StreamHandlerError<flutter::EncodableValue>
FlStreamHandlerError;

std::string BatteryStatusToString(BatteryStatus status) {
switch (status) {
case BatteryStatus::kCharging:
return "charging";
case BatteryStatus::kFull:
return "full";
case BatteryStatus::kDischarging:
return "discharging";
case BatteryStatus::kUnknown:
default:
return "unknown";
}
}

BatteryPlusTizenPlugin() {}

virtual ~BatteryPlusTizenPlugin() {}

void RegisterObserver(
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> &&events) {
class BatteryStatusStreamHandler : public FlStreamHandler {
protected:
std::unique_ptr<FlStreamHandlerError> OnListenInternal(
const flutter::EncodableValue *arguments,
std::unique_ptr<FlEventSink> &&events) override {
events_ = std::move(events);

// DEVICE_CALLBACK_BATTERY_CHARGING callback is called in only two cases
// like charging and discharing. When the charging status becomes "Full",
// discharging status event is called. So if it is full and disconnected
// from USB or AC charger, then any callbacks will not be called because the
// status already is the discharging status. To resolve this issue,
// DEVICE_CALLBACK_BATTERY_LEVEL callback is added. This callback can check
// whether it is disconnected in "Full" status. That is, when the battery
// status is full and disconnected, the level status will be changed from
// DEVICE_BATTERY_LEVEL_FULL to DEVICE_BATTERY_LEVEL_HIGH.
int ret = device_add_callback(DEVICE_CALLBACK_BATTERY_CHARGING,
BatteryChangedCB, this);
if (ret != DEVICE_ERROR_NONE) {
events_->Error("failed_to_add_callback", get_error_message(ret));
return;
BatteryStatusCallback callback = [this](BatteryStatus status) -> void {
if (status != BatteryStatus::kError) {
events_->Success(
flutter::EncodableValue(BatteryStatusToString(status)));
} else {
events_->Error(std::to_string(battery_.GetLastError()),
battery_.GetLastErrorString());
}
};
if (!battery_.StartListen(callback)) {
return std::make_unique<FlStreamHandlerError>(
std::to_string(battery_.GetLastError()),
battery_.GetLastErrorString(), nullptr);
Comment on lines +61 to +63
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently results in an app crash. There will be a fix in the engine code soon.

Issue: flutter/flutter#101682

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@swift-kim Any progress on this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bbrto21 It isn't likely the fix will land very soon. How do you think of this or this workaround?

Copy link
Contributor

@bbrto21 bbrto21 Sep 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@swift-kim Actually, I also made a patch to prevent crash. how about this?
First one has intentional leak and the second solution you mentioned doesn't seem very good as it depends on instances of other classes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The holder_ suffix seems redundant. Otherwise looks good to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@swift-kim Can I make PR to apply all place where have same problem?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Just one thing I forgot to mention: the private: section header is missing in the class.

}

ret = device_add_callback(DEVICE_CALLBACK_BATTERY_LEVEL, BatteryChangedCB,
this);
if (ret != DEVICE_ERROR_NONE) {
events_->Error("failed_to_add_callback", get_error_message(ret));
return;
}
// Send an initial event once the stream has been set up.
callback(battery_.GetStatus());

std::string status = GetBatteryStatus();
if (status.empty()) {
events_->Error("invalid_status", "Charging status error");
} else {
events_->Success(flutter::EncodableValue(status));
}
return nullptr;
}

void UnregisterObserver() {
int ret = device_remove_callback(DEVICE_CALLBACK_BATTERY_CHARGING,
BatteryChangedCB);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to run device_remove_callback (%d: %s)", ret,
get_error_message(ret));
}
ret =
device_remove_callback(DEVICE_CALLBACK_BATTERY_LEVEL, BatteryChangedCB);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to run device_remove_callback (%d: %s)", ret,
get_error_message(ret));
}

events_ = nullptr;
std::unique_ptr<FlStreamHandlerError> OnCancelInternal(
const flutter::EncodableValue *arguments) override {
battery_.StopListen();
events_.reset();
return nullptr;
}

static std::string GetBatteryStatus() {
device_battery_status_e status;
int ret = device_battery_get_status(&status);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to run device_battery_get_status (%d: %s)", ret,
get_error_message(ret));
return "";
}
private:
DeviceBattery battery_;
std::unique_ptr<FlEventSink> events_;
};

std::string value;
if (status == DEVICE_BATTERY_STATUS_CHARGING) {
value = "charging";
} else if (status == DEVICE_BATTERY_STATUS_FULL) {
value = "full";
} else if (status == DEVICE_BATTERY_STATUS_DISCHARGING ||
status == DEVICE_BATTERY_STATUS_NOT_CHARGING) {
value = "discharging";
}
LOG_INFO("battery status [%s]", value.c_str());
return value;
}
class BatteryPlusTizenPlugin : public flutter::Plugin {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrar *registrar) {
auto plugin = std::make_unique<BatteryPlusTizenPlugin>();

static void BatteryChangedCB(device_callback_e type, void *value,
void *user_data) {
if (!user_data) {
LOG_ERROR("Invalid user data");
return;
}
auto method_channel = std::make_unique<FlMethodChannel>(
registrar->messenger(), "dev.fluttercommunity.plus/battery",
&flutter::StandardMethodCodec::GetInstance());
method_channel->SetMethodCallHandler(
[plugin_pointer = plugin.get()](const auto &call, auto result) {
plugin_pointer->HandleMethodCall(call, std::move(result));
});

// DEVICE_CALLBACK_BATTERY_LEVEL callback is used only for checking whether
// the battery became a discharging status while it is full.
if (type == DEVICE_CALLBACK_BATTERY_LEVEL &&
intptr_t(value) < DEVICE_BATTERY_LEVEL_HIGH) {
return;
}
auto event_channel = std::make_unique<FlEventChannel>(
registrar->messenger(), "dev.fluttercommunity.plus/charging",
&flutter::StandardMethodCodec::GetInstance());
event_channel->SetStreamHandler(
std::make_unique<BatteryStatusStreamHandler>());

BatteryPlusTizenPlugin *plugin_pointer =
(BatteryPlusTizenPlugin *)user_data;
registrar->AddPlugin(std::move(plugin));
}

std::string status = GetBatteryStatus();
bool is_full = status == "full";
if (is_full && plugin_pointer->is_full_) {
// This function is called twice by registered callbacks when battery
// status is full. So, it needs to avoid the unnecessary second call.
return;
}
plugin_pointer->is_full_ = is_full;
BatteryPlusTizenPlugin() {}

if (status.empty()) {
plugin_pointer->events_->Error("invalid_status", "Charging status error");
} else {
plugin_pointer->events_->Success(flutter::EncodableValue(status));
}
}
virtual ~BatteryPlusTizenPlugin() {}

void HandleMethodCall(
const flutter::MethodCall<flutter::EncodableValue> &method_call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
private:
void HandleMethodCall(const FlMethodCall &method_call,
std::unique_ptr<FlMethodResult> result) {
const auto &method_name = method_call.method_name();

if (method_name == "getBatteryLevel") {
int percentage;
int ret = device_battery_get_percent(&percentage);
if (ret != DEVICE_ERROR_NONE) {
result->Error("internal_error", get_error_message(ret));
DeviceBattery battery;
int32_t level = battery.GetLevel();
if (level >= 0) {
result->Success(flutter::EncodableValue(level));
} else {
result->Error(std::to_string(battery.GetLastError()),
battery.GetLastErrorString());
}
LOG_INFO("battery percentage [%d]", percentage);
result->Success(flutter::EncodableValue(percentage));
} else {
result->NotImplemented();
}
}

private:
void SetupChannels(flutter::PluginRegistrar *registrar) {
auto method_channel =
std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
registrar->messenger(), "dev.fluttercommunity.plus/battery",
&flutter::StandardMethodCodec::GetInstance());
event_channel_ =
std::make_unique<flutter::EventChannel<flutter::EncodableValue>>(
registrar->messenger(), "dev.fluttercommunity.plus/charging",
&flutter::StandardMethodCodec::GetInstance());

auto method_channel_handler = [this](const auto &call, auto result) {
LOG_DEBUG("HandleMethodCall call");
HandleMethodCall(call, std::move(result));
};
auto event_channel_handler =
std::make_unique<flutter::StreamHandlerFunctions<>>(
[this](const flutter::EncodableValue *arguments,
std::unique_ptr<flutter::EventSink<>> &&events)
-> std::unique_ptr<flutter::StreamHandlerError<>> {
LOG_DEBUG("OnListen");
RegisterObserver(std::move(events));
return nullptr;
},
[this](const flutter::EncodableValue *arguments)
-> std::unique_ptr<flutter::StreamHandlerError<>> {
LOG_DEBUG("OnCancel");
UnregisterObserver();
return nullptr;
});

method_channel->SetMethodCallHandler(method_channel_handler);
event_channel_->SetStreamHandler(std::move(event_channel_handler));
}

std::unique_ptr<flutter::EventChannel<flutter::EncodableValue>>
event_channel_;
std::unique_ptr<flutter::EventSink<flutter::EncodableValue>> events_;
bool is_full_ = false;
};

} // namespace

void BatteryPlusTizenPluginRegisterWithRegistrar(
FlutterDesktopPluginRegistrarRef registrar) {
BatteryPlusTizenPlugin::RegisterWithRegistrar(
Expand Down
110 changes: 110 additions & 0 deletions packages/battery_plus/tizen/src/device_battery.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device_battery.h"

#include <device/battery.h>

#include "log.h"

bool DeviceBattery::StartListen(BatteryStatusCallback callback) {
// DEVICE_CALLBACK_BATTERY_CHARGING callback is called in only two cases
// like charging and discharing. When the charging status becomes "Full",
// discharging status event is called. So if it is full and disconnected
// from USB or AC charger, then any callbacks will not be called because the
// status already is the discharging status. To resolve this issue,
// DEVICE_CALLBACK_BATTERY_LEVEL callback is added. This callback can check
// whether it is disconnected in "Full" status. That is, when the battery
// status is full and disconnected, the level status will be changed from
// DEVICE_BATTERY_LEVEL_FULL to DEVICE_BATTERY_LEVEL_HIGH.
int ret = device_add_callback(DEVICE_CALLBACK_BATTERY_CHARGING,
OnBatteryStatusChanged, this);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to add callback: %s", get_error_message(ret));
last_error_ = ret;
return false;
}

ret = device_add_callback(DEVICE_CALLBACK_BATTERY_LEVEL,
OnBatteryStatusChanged, this);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to add callback: %s", get_error_message(ret));
last_error_ = ret;
return false;
}

callback_ = callback;
return true;
}

void DeviceBattery::StopListen() {
int ret = device_remove_callback(DEVICE_CALLBACK_BATTERY_CHARGING,
OnBatteryStatusChanged);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to remove callback: %s", get_error_message(ret));
last_error_ = ret;
}

ret = device_remove_callback(DEVICE_CALLBACK_BATTERY_LEVEL,
OnBatteryStatusChanged);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to remove callback: %s", get_error_message(ret));
last_error_ = ret;
}
}

int32_t DeviceBattery::GetLevel() {
int32_t level;
int ret = device_battery_get_percent(&level);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to get battery level: %s", get_error_message(ret));
last_error_ = ret;
return -1;
}
return level;
}

BatteryStatus DeviceBattery::GetStatus() {
device_battery_status_e status;
int ret = device_battery_get_status(&status);
if (ret != DEVICE_ERROR_NONE) {
LOG_ERROR("Failed to get battery status: %s", get_error_message(ret));
last_error_ = ret;
return BatteryStatus::kError;
}

switch (status) {
case DEVICE_BATTERY_STATUS_CHARGING:
return BatteryStatus::kCharging;
case DEVICE_BATTERY_STATUS_FULL:
return BatteryStatus::kFull;
case DEVICE_BATTERY_STATUS_DISCHARGING:
case DEVICE_BATTERY_STATUS_NOT_CHARGING:
return BatteryStatus::kDischarging;
default:
return BatteryStatus::kUnknown;
}
}

void DeviceBattery::OnBatteryStatusChanged(device_callback_e type, void *value,
void *user_data) {
auto *self = static_cast<DeviceBattery *>(user_data);

// DEVICE_CALLBACK_BATTERY_LEVEL callback is used only for checking whether
// the battery became a discharging status while it is full.
if (type == DEVICE_CALLBACK_BATTERY_LEVEL &&
intptr_t(value) < DEVICE_BATTERY_LEVEL_HIGH) {
return;
}

BatteryStatus status = self->GetStatus();
if (status == BatteryStatus::kFull && self->is_full_) {
// This function is called twice by registered callbacks when battery
// status is full. So, it needs to avoid the unnecessary second call.
return;
}
self->is_full_ = status == BatteryStatus::kFull;

self->callback_(status);
}
Loading