tdesktop/Telegram/SourceFiles/calls/calls_instance.cpp

356 lines
9.7 KiB
C++
Raw Normal View History

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_instance.h"
2019-11-13 14:12:04 +00:00
#include "mtproto/mtproto_dh_utils.h"
#include "core/application.h"
2019-07-24 11:45:24 +00:00
#include "main/main_session.h"
2020-06-25 17:57:36 +00:00
#include "main/main_account.h"
#include "apiwrap.h"
2017-04-13 08:27:10 +00:00
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "calls/calls_call.h"
#include "calls/calls_panel.h"
#include "data/data_user.h"
2019-01-18 12:27:37 +00:00
#include "data/data_session.h"
#include "media/audio/media_audio_track.h"
#include "platform/platform_specific.h"
#include "base/unixtime.h"
#include "mainwidget.h"
#include "mtproto/mtproto_config.h"
#include "boxes/rate_call_box.h"
#include "app.h"
namespace Calls {
namespace {
constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000);
} // namespace
2020-06-25 17:57:36 +00:00
Instance::Instance() = default;
Instance::~Instance() {
for (const auto panel : _pendingPanels) {
if (panel) {
delete panel;
}
}
2019-08-06 16:40:08 +00:00
}
void Instance::startOutgoingCall(not_null<UserData*> user) {
if (alreadyInCall()) { // Already in a call.
2017-04-27 21:17:00 +00:00
_currentCallPanel->showAndActivate();
2017-05-03 13:43:01 +00:00
return;
}
if (user->callsStatus() == UserData::CallsStatus::Private) {
// Request full user once more to refresh the setting in case it was changed.
2020-06-25 17:57:36 +00:00
user->session().api().requestFullPeer(user);
Ui::show(Box<InformBox>(
tr::lng_call_error_not_available(tr::now, lt_user, user->name)));
return;
}
2020-05-17 09:12:27 +00:00
requestPermissionsOrFail(crl::guard(this, [=] {
createCall(user, Call::Type::Outgoing);
2018-10-17 06:09:59 +00:00
}));
}
void Instance::callFinished(not_null<Call*> call) {
destroyCall(call);
}
void Instance::callFailed(not_null<Call*> call) {
destroyCall(call);
}
void Instance::callRedial(not_null<Call*> call) {
if (_currentCall.get() == call) {
refreshDhConfig();
}
}
2017-05-03 13:43:01 +00:00
void Instance::playSound(Sound sound) {
switch (sound) {
case Sound::Busy: {
if (!_callBusyTrack) {
_callBusyTrack = Media::Audio::Current().createTrack();
_callBusyTrack->fillFromFile(
Core::App().settings().getSoundPath(qsl("call_busy")));
2017-05-03 13:43:01 +00:00
}
_callBusyTrack->playOnce();
} break;
case Sound::Ended: {
if (!_callEndedTrack) {
_callEndedTrack = Media::Audio::Current().createTrack();
_callEndedTrack->fillFromFile(
Core::App().settings().getSoundPath(qsl("call_end")));
2017-05-03 13:43:01 +00:00
}
_callEndedTrack->playOnce();
} break;
case Sound::Connecting: {
if (!_callConnectingTrack) {
_callConnectingTrack = Media::Audio::Current().createTrack();
_callConnectingTrack->fillFromFile(
Core::App().settings().getSoundPath(qsl("call_connect")));
2017-05-03 13:43:01 +00:00
}
_callConnectingTrack->playOnce();
} break;
}
}
void Instance::destroyCall(not_null<Call*> call) {
if (_currentCall.get() == call) {
2017-05-04 13:32:56 +00:00
destroyCurrentPanel();
2020-06-25 17:57:36 +00:00
auto taken = base::take(_currentCall);
_currentCallChanges.fire(nullptr);
taken.reset();
if (App::quitting()) {
LOG(("Calls::Instance doesn't prevent quit any more."));
}
Core::App().quitPreventFinished();
}
}
2017-05-04 13:32:56 +00:00
void Instance::destroyCurrentPanel() {
_pendingPanels.erase(
std::remove_if(
_pendingPanels.begin(),
_pendingPanels.end(),
[](auto &&panel) { return !panel; }),
_pendingPanels.end());
_pendingPanels.emplace_back(_currentCallPanel.release());
2017-05-04 13:32:56 +00:00
_pendingPanels.back()->hideAndDestroy(); // Always queues the destruction.
}
void Instance::createCall(not_null<UserData*> user, Call::Type type) {
2020-06-25 17:57:36 +00:00
auto call = std::make_unique<Call>(getCallDelegate(), user, type);
const auto raw = call.get();
user->session().account().sessionChanges(
) | rpl::start_with_next([=] {
destroyCall(raw);
}, raw->lifetime());
if (_currentCall) {
2020-06-25 17:57:36 +00:00
_currentCallPanel->replaceCall(raw);
std::swap(_currentCall, call);
call->hangup();
} else {
2020-06-25 17:57:36 +00:00
_currentCallPanel = std::make_unique<Panel>(raw);
_currentCall = std::move(call);
}
2020-06-25 17:57:36 +00:00
_currentCallChanges.fire_copy(raw);
refreshServerConfig(&user->session());
refreshDhConfig();
}
void Instance::refreshDhConfig() {
Expects(_currentCall != nullptr);
const auto weak = base::make_weak(_currentCall);
2020-06-25 17:57:36 +00:00
_currentCall->user()->session().api().request(MTPmessages_GetDhConfig(
MTP_int(_dhConfig.version),
MTP_int(MTP::ModExpFirst::kRandomPowerSize)
)).done([=](const MTPmessages_DhConfig &result) {
const auto call = weak.get();
const auto random = updateDhConfig(result);
if (!call) {
return;
}
if (!random.empty()) {
Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize);
call->start(random);
} else {
callFailed(call);
}
}).fail([=](const RPCError &error) {
const auto call = weak.get();
if (!call) {
return;
}
callFailed(call);
}).send();
}
bytes::const_span Instance::updateDhConfig(
const MTPmessages_DhConfig &data) {
const auto validRandom = [](const QByteArray & random) {
if (random.size() != MTP::ModExpFirst::kRandomPowerSize) {
return false;
}
return true;
};
return data.match([&](const MTPDmessages_dhConfig &data)
-> bytes::const_span {
2019-07-05 13:38:38 +00:00
auto primeBytes = bytes::make_vector(data.vp().v);
if (!MTP::IsPrimeAndGood(primeBytes, data.vg().v)) {
LOG(("API Error: bad p/g received in dhConfig."));
return {};
2019-07-05 13:38:38 +00:00
} else if (!validRandom(data.vrandom().v)) {
return {};
}
2019-07-05 13:38:38 +00:00
_dhConfig.g = data.vg().v;
_dhConfig.p = std::move(primeBytes);
2019-07-05 13:38:38 +00:00
_dhConfig.version = data.vversion().v;
return bytes::make_span(data.vrandom().v);
}, [&](const MTPDmessages_dhConfigNotModified &data)
-> bytes::const_span {
if (!_dhConfig.g || _dhConfig.p.empty()) {
LOG(("API Error: dhConfigNotModified on zero version."));
return {};
2019-07-05 13:38:38 +00:00
} else if (!validRandom(data.vrandom().v)) {
return {};
}
2019-07-05 13:38:38 +00:00
return bytes::make_span(data.vrandom().v);
});
}
2020-06-25 17:57:36 +00:00
void Instance::refreshServerConfig(not_null<Main::Session*> session) {
if (_serverConfigRequestSession) {
return;
}
2020-06-25 17:57:36 +00:00
if (_lastServerConfigUpdateTime
&& ((crl::now() - _lastServerConfigUpdateTime)
< kServerConfigUpdateTimeoutMs)) {
return;
}
2020-06-25 17:57:36 +00:00
_serverConfigRequestSession = session;
session->api().request(MTPphone_GetCallConfig(
2019-11-27 08:02:56 +00:00
)).done([=](const MTPDataJSON &result) {
2020-06-25 17:57:36 +00:00
_serverConfigRequestSession = nullptr;
_lastServerConfigUpdateTime = crl::now();
2019-07-05 13:38:38 +00:00
const auto &json = result.c_dataJSON().vdata().v;
2019-01-05 11:08:02 +00:00
UpdateConfig(std::string(json.data(), json.size()));
2019-11-27 08:02:56 +00:00
}).fail([=](const RPCError &error) {
2020-06-25 17:57:36 +00:00
_serverConfigRequestSession = nullptr;
}).send();
}
2020-06-25 17:57:36 +00:00
void Instance::handleUpdate(
not_null<Main::Session*> session,
const MTPUpdate &update) {
update.match([&](const MTPDupdatePhoneCall &data) {
handleCallUpdate(session, data.vphone_call());
}, [&](const MTPDupdatePhoneCallSignalingData &data) {
handleSignalingData(data);
}, [](const auto &) {
Unexpected("Update type in Calls::Instance::handleUpdate.");
});
}
void Instance::showInfoPanel(not_null<Call*> call) {
if (_currentCall.get() == call) {
_currentCallPanel->showAndActivate();
}
}
bool Instance::isQuitPrevent() {
if (!_currentCall || _currentCall->isIncomingWaiting()) {
return false;
}
_currentCall->hangup();
if (!_currentCall) {
return false;
}
LOG(("Calls::Instance prevents quit, hanging up a call..."));
return true;
}
2020-06-25 17:57:36 +00:00
void Instance::handleCallUpdate(
not_null<Main::Session*> session,
const MTPPhoneCall &call) {
if (call.type() == mtpc_phoneCallRequested) {
auto &phoneCall = call.c_phoneCallRequested();
2020-06-25 17:57:36 +00:00
auto user = session->data().userLoaded(phoneCall.vadmin_id().v);
if (!user) {
LOG(("API Error: User not loaded for phoneCallRequested."));
} else if (user->isSelf()) {
LOG(("API Error: Self found in phoneCallRequested."));
}
2020-06-25 17:57:36 +00:00
const auto &config = session->serverConfig();
if (alreadyInCall() || !user || user->isSelf()) {
2020-06-25 17:57:36 +00:00
session->api().request(MTPphone_DiscardCall(
2019-03-22 13:42:03 +00:00
MTP_flags(0),
2019-07-05 13:38:38 +00:00
MTP_inputPhoneCall(phoneCall.vid(), phoneCall.vaccess_hash()),
2019-03-22 13:42:03 +00:00
MTP_int(0),
MTP_phoneCallDiscardReasonBusy(),
MTP_long(0)
)).send();
} else if (phoneCall.vdate().v + (config.callRingTimeoutMs / 1000)
< base::unixtime::now()) {
LOG(("Ignoring too old call."));
} else {
createCall(user, Call::Type::Incoming);
_currentCall->handleUpdate(call);
}
} else if (!_currentCall || !_currentCall->handleUpdate(call)) {
DEBUG_LOG(("API Warning: unexpected phone call update %1").arg(call.type()));
}
}
void Instance::handleSignalingData(
const MTPDupdatePhoneCallSignalingData &data) {
if (!_currentCall || !_currentCall->handleSignalingData(data)) {
DEBUG_LOG(("API Warning: unexpected call signaling data %1"
).arg(data.vphone_call_id().v));
}
}
bool Instance::alreadyInCall() {
return (_currentCall && _currentCall->state() != Call::State::Busy);
2017-05-03 13:43:01 +00:00
}
2018-10-17 06:09:59 +00:00
2020-06-25 17:57:36 +00:00
Call *Instance::currentCall() const {
2019-01-05 11:08:02 +00:00
return _currentCall.get();
}
2020-06-25 17:57:36 +00:00
rpl::producer<Call*> Instance::currentCallValue() const {
return _currentCallChanges.events_starting_with(currentCall());
}
2020-05-17 09:12:27 +00:00
void Instance::requestPermissionsOrFail(Fn<void()> onSuccess) {
using Type = Platform::PermissionType;
requestPermissionOrFail(Type::Microphone, [=] {
requestPermissionOrFail(Type::Camera, [=] {
crl::on_main(onSuccess);
});
});
}
void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn<void()> onSuccess) {
using Status = Platform::PermissionStatus;
const auto status = Platform::GetPermissionStatus(type);
if (status == Status::Granted) {
onSuccess();
2020-05-17 09:12:27 +00:00
} else if (status == Status::CanRequest) {
Platform::RequestPermission(type, crl::guard(this, [=](Status status) {
if (status == Status::Granted) {
crl::on_main(onSuccess);
} else {
if (_currentCall) {
_currentCall->hangup();
}
}
2018-10-17 06:09:59 +00:00
}));
} else {
if (alreadyInCall()) {
_currentCall->hangup();
}
2020-05-17 09:12:27 +00:00
Ui::show(Box<ConfirmBox>(tr::lng_no_mic_permission(tr::now), tr::lng_menu_settings(tr::now), crl::guard(this, [=] {
Platform::OpenSystemSettingsForPermission(type);
Ui::hideLayer();
2018-10-17 06:09:59 +00:00
})));
}
}
2017-05-03 13:43:01 +00:00
} // namespace Calls