Merge pull request #988 from Heap-Hop/ignore_battery_optimizations

Update Android
This commit is contained in:
RustDesk 2022-07-18 10:47:37 +08:00 committed by GitHub
commit dcf14dbb74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 127 additions and 71 deletions

View File

@ -192,7 +192,6 @@ class MainActivity : FlutterActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val inputPer = InputService.isOpen val inputPer = InputService.isOpen
Log.d(logTag, "onResume inputPer:$inputPer")
activity.runOnUiThread { activity.runOnUiThread {
flutterMethodChannel.invokeMethod( flutterMethodChannel.invokeMethod(
"on_state_changed", "on_state_changed",

View File

@ -57,6 +57,18 @@ fun requestPermission(context: Context, type: String) {
} }
return return
} }
"application_details_settings" -> {
try {
context.startActivity(Intent().apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
action = "android.settings.APPLICATION_DETAILS_SETTINGS"
data = Uri.parse("package:" + context.packageName)
})
} catch (e:Exception) {
e.printStackTrace()
}
return
}
"audio" -> { "audio" -> {
Permission.RECORD_AUDIO Permission.RECORD_AUDIO
} }

View File

@ -260,7 +260,12 @@ class PermissionManager {
static Timer? _timer; static Timer? _timer;
static var _current = ""; static var _current = "";
static final permissions = ["audio", "file", "ignore_battery_optimizations"]; static final permissions = [
"audio",
"file",
"ignore_battery_optimizations",
"application_details_settings"
];
static bool isWaitingFile() { static bool isWaitingFile() {
if (_completer != null) { if (_completer != null) {

View File

@ -26,23 +26,42 @@ class SettingsPage extends StatefulWidget implements PageShape {
_SettingsState createState() => _SettingsState(); _SettingsState createState() => _SettingsState();
} }
class _SettingsState extends State<SettingsPage> { class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
static const url = 'https://rustdesk.com/'; static const url = 'https://rustdesk.com/';
var _showIgnoreBattery = false; final _hasIgnoreBattery = androidVersion >= 26;
var _ignoreBatteryOpt = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (androidVersion >= 26) { WidgetsBinding.instance.addObserver(this);
() async { if (_hasIgnoreBattery) {
final res = updateIgnoreBatteryStatus();
await PermissionManager.check("ignore_battery_optimizations"); }
if (_showIgnoreBattery != !res) { }
setState(() {
_showIgnoreBattery = !res; @override
}); void dispose() {
} WidgetsBinding.instance.removeObserver(this);
}(); super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
updateIgnoreBatteryStatus();
}
}
Future<bool> updateIgnoreBatteryStatus() async {
final res = await PermissionManager.check("ignore_battery_optimizations");
if (_ignoreBatteryOpt != res) {
setState(() {
_ignoreBatteryOpt = res;
});
return true;
} else {
return false;
} }
} }
@ -53,7 +72,6 @@ class _SettingsState extends State<SettingsPage> {
final enableAbr = FFI.getByName("option", "enable-abr") != 'N'; final enableAbr = FFI.getByName("option", "enable-abr") != 'N';
final enhancementsTiles = [ final enhancementsTiles = [
SettingsTile.switchTile( SettingsTile.switchTile(
leading: Icon(Icons.more_horiz),
title: Text(translate('Adaptive Bitrate') + '(beta)'), title: Text(translate('Adaptive Bitrate') + '(beta)'),
initialValue: enableAbr, initialValue: enableAbr,
onToggle: (v) { onToggle: (v) {
@ -68,32 +86,37 @@ class _SettingsState extends State<SettingsPage> {
}, },
) )
]; ];
if (_showIgnoreBattery) { if (_hasIgnoreBattery) {
enhancementsTiles.insert( enhancementsTiles.insert(
0, 0,
SettingsTile.navigation( SettingsTile.switchTile(
initialValue: _ignoreBatteryOpt,
title: Text(translate('Keep RustDesk background service')), title: Text(translate('Keep RustDesk background service')),
description: description:
Text('* ${translate('Ignore Battery Optimizations')}'), Text('* ${translate('Ignore Battery Optimizations')}'),
leading: Icon(Icons.battery_saver), onToggle: (v) async {
onPressed: (context) { if (v) {
PermissionManager.request("ignore_battery_optimizations"); PermissionManager.request("ignore_battery_optimizations");
var count = 0; } else {
Timer.periodic(Duration(seconds: 1), (timer) async { final res = await DialogManager.show<bool>(
if (count > 5) { (setState, close) => CustomAlertDialog(
count = 0; title: Text(translate("Open System Setting")),
timer.cancel(); content: Text(translate(
"android_open_battery_optimizations_tip")),
actions: [
TextButton(
onPressed: () => close(),
child: Text(translate("Cancel"))),
ElevatedButton(
onPressed: () => close(true),
child:
Text(translate("Open System Setting"))),
],
));
if (res == true) {
PermissionManager.request("application_details_settings");
} }
if (await PermissionManager.check( }
"ignore_battery_optimizations")) {
count = 0;
timer.cancel();
setState(() {
_showIgnoreBattery = false;
});
}
count++;
});
})); }));
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", "语言"), ("Language", "语言"),
("Keep RustDesk background service", "保持RustDesk后台服务"), ("Keep RustDesk background service", "保持RustDesk后台服务"),
("Ignore Battery Optimizations", "忽略电池优化"), ("Ignore Battery Optimizations", "忽略电池优化"),
("android_open_battery_optimizations_tip", "如需关闭此功能请在接下来的RustDesk应用设置页面中找到并进入 [电源] 页面,取消勾选 [不受限制]"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", "Sprache"), ("Language", "Sprache"),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -27,5 +27,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"),
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), ("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
("server_not_support", "Not yet supported by the server"), ("server_not_support", "Not yet supported by the server"),
("android_open_battery_optimizations_tip", "If you want to disable this feature, please go to the next RustDesk application settings page, find and enter [Battery] ,Uncheck [Unrestricted]"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", "Langue"), ("Language", "Langue"),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", "Nyelv"), ("Language", "Nyelv"),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", "Linguaggio"), ("Language", "Linguaggio"),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", "Язык"), ("Language", "Язык"),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", ""), ("Language", ""),
("Keep RustDesk background service", ""), ("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""), ("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -286,5 +286,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Language", "語言"), ("Language", "語言"),
("Keep RustDesk background service", "保持RustDesk後台服務"), ("Keep RustDesk background service", "保持RustDesk後台服務"),
("Ignore Battery Optimizations", "忽略電池優化"), ("Ignore Battery Optimizations", "忽略電池優化"),
("android_open_battery_optimizations_tip", "如需關閉此功能請在接下來的RustDesk應用設置頁面中找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -599,7 +599,7 @@ impl Connection {
async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool { async fn handle_msg_from_peer(&mut self, data: &[u8], peer: &mut Stream) -> bool {
if let Ok(msg_in) = Message::parse_from_bytes(&data) { if let Ok(msg_in) = Message::parse_from_bytes(&data) {
match msg_in.union { match msg_in.union {
Some(message::Union::video_frame(vf)) => { Some(message::Union::VideoFrame(vf)) => {
if !self.first_frame { if !self.first_frame {
self.first_frame = true; self.first_frame = true;
} }
@ -610,21 +610,21 @@ impl Connection {
s.add(ZeroCopyBuffer(self.video_handler.rgb.clone())); s.add(ZeroCopyBuffer(self.video_handler.rgb.clone()));
} }
} }
Some(message::Union::hash(hash)) => { Some(message::Union::Hash(hash)) => {
self.session.handle_hash(hash, peer).await; self.session.handle_hash(hash, peer).await;
} }
Some(message::Union::login_response(lr)) => match lr.union { Some(message::Union::LoginResponse(lr)) => match lr.union {
Some(login_response::Union::error(err)) => { Some(login_response::Union::Error(err)) => {
if !self.session.handle_login_error(&err) { if !self.session.handle_login_error(&err) {
return false; return false;
} }
} }
Some(login_response::Union::peer_info(pi)) => { Some(login_response::Union::PeerInfo(pi)) => {
self.session.handle_peer_info(pi); self.session.handle_peer_info(pi);
} }
_ => {} _ => {}
}, },
Some(message::Union::clipboard(cb)) => { Some(message::Union::Clipboard(cb)) => {
if !self.session.lc.read().unwrap().disable_clipboard { if !self.session.lc.read().unwrap().disable_clipboard {
let content = if cb.compress { let content = if cb.compress {
decompress(&cb.content) decompress(&cb.content)
@ -637,7 +637,7 @@ impl Connection {
} }
} }
} }
Some(message::Union::cursor_data(cd)) => { Some(message::Union::CursorData(cd)) => {
let colors = hbb_common::compress::decompress(&cd.colors); let colors = hbb_common::compress::decompress(&cd.colors);
self.session.push_event( self.session.push_event(
"cursor_data", "cursor_data",
@ -654,18 +654,18 @@ impl Connection {
], ],
); );
} }
Some(message::Union::cursor_id(id)) => { Some(message::Union::CursorId(id)) => {
self.session self.session
.push_event("cursor_id", vec![("id", &id.to_string())]); .push_event("cursor_id", vec![("id", &id.to_string())]);
} }
Some(message::Union::cursor_position(cp)) => { Some(message::Union::CursorPosition(cp)) => {
self.session.push_event( self.session.push_event(
"cursor_position", "cursor_position",
vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())], vec![("x", &cp.x.to_string()), ("y", &cp.y.to_string())],
); );
} }
Some(message::Union::file_response(fr)) => match fr.union { Some(message::Union::FileResponse(fr)) => match fr.union {
Some(file_response::Union::dir(fd)) => { Some(file_response::Union::Dir(fd)) => {
let mut entries = fd.entries.to_vec(); let mut entries = fd.entries.to_vec();
if self.session.peer_platform() == "Windows" { if self.session.peer_platform() == "Windows" {
fs::transform_windows_path(&mut entries); fs::transform_windows_path(&mut entries);
@ -679,7 +679,7 @@ impl Connection {
job.set_files(entries); job.set_files(entries);
} }
} }
Some(file_response::Union::block(block)) => { Some(file_response::Union::Block(block)) => {
if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) { if let Some(job) = fs::get_job(block.id, &mut self.write_jobs) {
if let Err(_err) = job.write(block, None).await { if let Err(_err) = job.write(block, None).await {
// to-do: add "skip" for writing job // to-do: add "skip" for writing job
@ -687,17 +687,17 @@ impl Connection {
self.update_jobs_status(); self.update_jobs_status();
} }
} }
Some(file_response::Union::done(d)) => { Some(file_response::Union::Done(d)) => {
if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) { if let Some(job) = fs::get_job(d.id, &mut self.write_jobs) {
job.modify_time(); job.modify_time();
fs::remove_job(d.id, &mut self.write_jobs); fs::remove_job(d.id, &mut self.write_jobs);
} }
self.handle_job_status(d.id, d.file_num, None); self.handle_job_status(d.id, d.file_num, None);
} }
Some(file_response::Union::error(e)) => { Some(file_response::Union::Error(e)) => {
self.handle_job_status(e.id, e.file_num, Some(e.error)); self.handle_job_status(e.id, e.file_num, Some(e.error));
} }
Some(file_response::Union::digest(digest)) => { Some(file_response::Union::Digest(digest)) => {
if digest.is_upload { if digest.is_upload {
if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) { if let Some(job) = fs::get_job(digest.id, &mut self.read_jobs) {
if let Some(file) = job.files().get(digest.file_num as usize) { if let Some(file) = job.files().get(digest.file_num as usize) {
@ -708,9 +708,9 @@ impl Connection {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
union: Some(if overwrite { union: Some(if overwrite {
file_transfer_send_confirm_request::Union::offset_blk(0) file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else { } else {
file_transfer_send_confirm_request::Union::skip( file_transfer_send_confirm_request::Union::Skip(
true, true,
) )
}), }),
@ -740,7 +740,7 @@ impl Connection {
let msg= new_send_confirm(FileTransferSendConfirmRequest { let msg= new_send_confirm(FileTransferSendConfirmRequest {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::skip(true)), union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
..Default::default() ..Default::default()
}); });
self.session.send_msg(msg); self.session.send_msg(msg);
@ -752,9 +752,9 @@ impl Connection {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
union: Some(if overwrite { union: Some(if overwrite {
file_transfer_send_confirm_request::Union::offset_blk(0) file_transfer_send_confirm_request::Union::OffsetBlk(0)
} else { } else {
file_transfer_send_confirm_request::Union::skip(true) file_transfer_send_confirm_request::Union::Skip(true)
}), }),
..Default::default() ..Default::default()
}, },
@ -774,7 +774,7 @@ impl Connection {
FileTransferSendConfirmRequest { FileTransferSendConfirmRequest {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)), union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
..Default::default() ..Default::default()
}, },
); );
@ -791,15 +791,15 @@ impl Connection {
} }
_ => {} _ => {}
}, },
Some(message::Union::misc(misc)) => match misc.union { Some(message::Union::Misc(misc)) => match misc.union {
Some(misc::Union::audio_format(f)) => { Some(misc::Union::AudioFormat(f)) => {
self.audio_handler.handle_format(f); // self.audio_handler.handle_format(f); //
} }
Some(misc::Union::chat_message(c)) => { Some(misc::Union::ChatMessage(c)) => {
self.session self.session
.push_event("chat_client_mode", vec![("text", &c.text)]); .push_event("chat_client_mode", vec![("text", &c.text)]);
} }
Some(misc::Union::permission_info(p)) => { Some(misc::Union::PermissionInfo(p)) => {
log::info!("Change permission {:?} -> {}", p.permission, p.enabled); log::info!("Change permission {:?} -> {}", p.permission, p.enabled);
use permission_info::Permission; use permission_info::Permission;
self.session.push_event( self.session.push_event(
@ -815,7 +815,7 @@ impl Connection {
)], )],
); );
} }
Some(misc::Union::switch_display(s)) => { Some(misc::Union::SwitchDisplay(s)) => {
self.video_handler.reset(); self.video_handler.reset();
self.session.push_event( self.session.push_event(
"switch_display", "switch_display",
@ -828,22 +828,22 @@ impl Connection {
], ],
); );
} }
Some(misc::Union::close_reason(c)) => { Some(misc::Union::CloseReason(c)) => {
self.session.msgbox("error", "Connection Error", &c); self.session.msgbox("error", "Connection Error", &c);
return false; return false;
} }
_ => {} _ => {}
}, },
Some(message::Union::test_delay(t)) => { Some(message::Union::TestDelay(t)) => {
self.session.handle_test_delay(t, peer).await; self.session.handle_test_delay(t, peer).await;
} }
Some(message::Union::audio_frame(frame)) => { Some(message::Union::AudioFrame(frame)) => {
if !self.session.lc.read().unwrap().disable_audio { if !self.session.lc.read().unwrap().disable_audio {
self.audio_handler.handle_frame(frame); self.audio_handler.handle_frame(frame);
} }
} }
Some(message::Union::file_action(action)) => match action.union { Some(message::Union::FileAction(action)) => match action.union {
Some(file_action::Union::send_confirm(c)) => { Some(file_action::Union::SendConfirm(c)) => {
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) { if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
job.confirm(&c); job.confirm(&c);
} }
@ -1029,9 +1029,9 @@ impl Connection {
id, id,
file_num, file_num,
union: if need_override { union: if need_override {
Some(file_transfer_send_confirm_request::Union::offset_blk(0)) Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
} else { } else {
Some(file_transfer_send_confirm_request::Union::skip(true)) Some(file_transfer_send_confirm_request::Union::Skip(true))
}, },
..Default::default() ..Default::default()
}); });
@ -1047,9 +1047,9 @@ impl Connection {
id, id,
file_num, file_num,
union: if need_override { union: if need_override {
Some(file_transfer_send_confirm_request::Union::offset_blk(0)) Some(file_transfer_send_confirm_request::Union::OffsetBlk(0))
} else { } else {
Some(file_transfer_send_confirm_request::Union::skip(true)) Some(file_transfer_send_confirm_request::Union::Skip(true))
}, },
..Default::default() ..Default::default()
}); });
@ -1451,7 +1451,7 @@ pub mod connection_manager {
let mut req = FileTransferSendConfirmRequest { let mut req = FileTransferSendConfirmRequest {
id, id,
file_num, file_num,
union: Some(file_transfer_send_confirm_request::Union::offset_blk(0)), union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
..Default::default() ..Default::default()
}; };
let digest = FileTransferDigest { let digest = FileTransferDigest {