From 4925af69e2afd273b0fc81274eb788b5182c152e Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Wed, 19 Apr 2017 23:25:48 +0300
Subject: [PATCH] Add Calls::Panel and handle incoming calls.

---
 Telegram/Resources/colors.palette             |  12 +
 Telegram/Resources/icons/call_answer.png      | Bin 0 -> 456 bytes
 Telegram/Resources/icons/call_answer@2x.png   | Bin 0 -> 1205 bytes
 Telegram/Resources/icons/call_discard.png     | Bin 0 -> 405 bytes
 Telegram/Resources/icons/call_discard@2x.png  | Bin 0 -> 736 bytes
 .../Resources/icons/call_record_active.png    | Bin 0 -> 412 bytes
 .../Resources/icons/call_record_active@2x.png | Bin 0 -> 800 bytes
 .../Resources/icons/call_record_muted.png     | Bin 0 -> 459 bytes
 .../Resources/icons/call_record_muted@2x.png  | Bin 0 -> 843 bytes
 Telegram/Resources/icons/call_shadow_left.png | Bin 0 -> 100 bytes
 .../Resources/icons/call_shadow_left@2x.png   | Bin 0 -> 125 bytes
 Telegram/Resources/icons/call_shadow_top.png  | Bin 0 -> 103 bytes
 .../Resources/icons/call_shadow_top@2x.png    | Bin 0 -> 127 bytes
 .../Resources/icons/call_shadow_top_left.png  | Bin 0 -> 295 bytes
 .../icons/call_shadow_top_left@2x.png         | Bin 0 -> 559 bytes
 Telegram/SourceFiles/base/openssl_help.h      | 146 ++++++
 Telegram/SourceFiles/base/weak_unique_ptr.h   |   8 +-
 Telegram/SourceFiles/calls/calls.style        |  80 ++++
 Telegram/SourceFiles/calls/calls_call.cpp     | 423 +++++++++++++-----
 Telegram/SourceFiles/calls/calls_call.h       |  81 +++-
 Telegram/SourceFiles/calls/calls_instance.cpp |  63 ++-
 Telegram/SourceFiles/calls/calls_instance.h   |   9 +-
 Telegram/SourceFiles/calls/calls_panel.cpp    | 320 +++++++++++++
 Telegram/SourceFiles/calls/calls_panel.h      |  89 ++++
 Telegram/SourceFiles/core/utils.h             |   4 +
 Telegram/SourceFiles/messenger.cpp            |   9 +
 Telegram/SourceFiles/messenger.h              |   1 +
 Telegram/SourceFiles/structs.h                |   3 +-
 Telegram/SourceFiles/ui/widgets/widgets.style |   5 +
 Telegram/gyp/Telegram.gyp                     |   1 +
 Telegram/gyp/telegram_sources.txt             |   3 +
 31 files changed, 1101 insertions(+), 156 deletions(-)
 create mode 100644 Telegram/Resources/icons/call_answer.png
 create mode 100644 Telegram/Resources/icons/call_answer@2x.png
 create mode 100644 Telegram/Resources/icons/call_discard.png
 create mode 100644 Telegram/Resources/icons/call_discard@2x.png
 create mode 100644 Telegram/Resources/icons/call_record_active.png
 create mode 100644 Telegram/Resources/icons/call_record_active@2x.png
 create mode 100644 Telegram/Resources/icons/call_record_muted.png
 create mode 100644 Telegram/Resources/icons/call_record_muted@2x.png
 create mode 100644 Telegram/Resources/icons/call_shadow_left.png
 create mode 100644 Telegram/Resources/icons/call_shadow_left@2x.png
 create mode 100644 Telegram/Resources/icons/call_shadow_top.png
 create mode 100644 Telegram/Resources/icons/call_shadow_top@2x.png
 create mode 100644 Telegram/Resources/icons/call_shadow_top_left.png
 create mode 100644 Telegram/Resources/icons/call_shadow_top_left@2x.png
 create mode 100644 Telegram/SourceFiles/base/openssl_help.h
 create mode 100644 Telegram/SourceFiles/calls/calls.style
 create mode 100644 Telegram/SourceFiles/calls/calls_panel.cpp
 create mode 100644 Telegram/SourceFiles/calls/calls_panel.h

diff --git a/Telegram/Resources/colors.palette b/Telegram/Resources/colors.palette
index 787617cba..370c2fbb2 100644
--- a/Telegram/Resources/colors.palette
+++ b/Telegram/Resources/colors.palette
@@ -507,3 +507,15 @@ mediaviewTransparentFg: #cccccc; // another transparent filling part
 
 // notification
 notificationBg: windowBg; // custom notification window background
+
+// calls
+callBg: #26282cf2;
+callNameFg: #ffffff;
+callFingerprintBg: #00000066;
+callStatusFg: #aaabac;
+callIconFg: #ffffff;
+callAnswerBg: #64c15b;
+callAnswerRipple: #52b149;
+callHangupBg: #d75a5a;
+callHangupRipple: #c04646;
+callMuteRipple: #ffffff12;
diff --git a/Telegram/Resources/icons/call_answer.png b/Telegram/Resources/icons/call_answer.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5a9ceb7329b3844598e6db730463b754b85c8c9
GIT binary patch
literal 456
zcmV;(0XP1MP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0004xNkl<ZNQvE*
zF|Wa37==$=A+%kJk&=kTV6m7D#9E7g;V0b0Y!r!v@N@b}*tPKw41~ys<lKp<l8d^_
zJ;@o~<m>zNyl=l$0YLnrLVwx(p8|vskWyYenM|N*S|A9oEv4kOY};lom*efZXYEU{
zTCF^FDwRS!9)J7oeE`>WJ#{*rhH09g15Bq=PaO`210?`%W>&3MJv$f-1~d%g?H%|4
zi9~{q<6N4nsv4yE{U!JNeE`5_v%zk+3p5^I0su5kldkJ>y<W3cs|8AM7XW~kWpTgX
zQwYIAq3|}~3&8iiSdQZ$5{aPIYN1#x!nSSC9np0imSrKE%_16&LRD3qPA41=hu=qZ
zwyx{!^?D@9*=)voy-om(#bO+dMwc&bxm;2R!SQ(f{D{uhG>x53ha|b(ZrSa2IUEl8
zNG}5bFcb>0*=%yLSUhY@l2Xd|0B2`18MfQ)%RRZ(@p$w-qns(vN>LOThJjM4ghr!*
ya=DCrK96KFiTQkvN~PjG|0>{B{lj18r}YExsHWP4P`tqa0000<MNUMnLSTZZr_sp(

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_answer@2x.png b/Telegram/Resources/icons/call_answer@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..d6eb7c010c9437be2876f6187ec32f8dd5e9be33
GIT binary patch
literal 1205
zcmV;m1WNmfP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm000DhNkl<ZXo2mS
zJ5MrO6o%g!9R~#>0wQ=L5W>Yv)EFp?u`ya`VWWl7SlH1R3xB|grG-8I27iLl-gu2B
zN&*<MFeo7iLAeaGPT}!OIQp>%1v%e|PqHg!R^Er%VXfJ400@6De7ysFl^=n6^CM7i
zegx_bQ{eLQ5|Si=h~6z(mXXP15C{Y?Ffi~Pc|U~8ElHAyMKl^Euh&bWD3+~Ni^Y5b
zJkQ=`EiEn3>2wf9k^Oa58D=^n_N2k{JX9(b1VN}us7hc8$g<2VA_xMEMk5*<8><qk
z5|{$HT#i{p5CqumcBoXUs)VWpra(HKW)=|y0WOydYPA|oO-)q^rWB??JRWBk;dwZn
zPG~fmZ{J2tflw%<sF1;6fJ&tT$8lc{RS8UiU@(Y6p};J3cXx+GB7tJDSe0N(VG3Md
zUo)RVBoe{)_BLX%7_!-H)xs%(DR6UhgTuqa-+ms6M6kKJiKC+<JU%{FHJnn)wrZ!-
zNq)bda=F~Q5h4@{(c<DFSuB>ij1m8=@)wv)CK?|fr}OjklJK!ujMmrJsjI82Ucr^k
zLa9^=(P$KVdwV5andf;VlgV%2^Z%v+z+dGqkR%DREaUn489tv6?d|OVfad0ANRou(
z<6}HLJbXjck6{V`Kt7*GE|)`ldpo>dFL<5@$8oUPYzT!yxWB(gDwV2z+7D19004zT
z0jX39UauEsvl+na2;p|Sae8`+$H&LIY$wG6k|ZIY&m)`7Vr*>e*WlLH)&`5kg0r(T
zB$G)+O|P0L764u+(|9}%tyYWC(a~RHgVkz<Mx#M67{t@l6C_Du))GY#EiEk&1OXh!
zeY%5JB=GhQe0O&Tm&*m4%?1GAI1V0<2hGjR2#3Q+r_*Jhgu!5d&1Qqs>4aXdheo4;
zTCIjkrGhAm5Cj24q)4hF0RSi#i^%12xV^o_@bEB9CKG_ypVj{Ue(3dj+}_?IlgZ%a
z<>lS|-QC@on3%xU))tnRmoYUpg`uG#Sglr=OeW~{dYH{-=yW>hbUH=d{ZkFZXf)FN
z{5(Y>k&*@{NfI3#9MJ6SEOmExm)v8qSZH;1m2PfsO71BZi<He~DV<JJB9WkYJWkPQ
zlmdak*E?~!)!EreD=RA$4u?wu$+Aq5NQBnb)@Wd0fV#T6sHdlgmX?;@36~?2$<$T=
z0GZ8ZT3A@1i;D}AWw|84^Yb(9?(UM`@28E8jd!9-sJXz~cAB1^rv3eWDijLuLd4^7
zdU$xKIQ+K>0HD^^RvH-@p`D!_N+c4W3R_nJ03-+kIUEl1`~7rsazgohzUBhln?*J0
z=;(mM;lSM79A;)_;PH4Ao$!4G>m9o$0>EphH5d$Vxm=i?oyFwjB>MXLDn53po4^|y
zjRppT0lmGw7#tjg*XxDP=YzxHV9FGWMdmS0%>{thcL%Lj3!P2}v)K&0-43VI3Aft~
zhr<D*(Fm<p3sDr&(9i&m;~>j2uCA^yI5=2;fm-ta|A*$k&W}L7`4Ol$f4soo)AnM)
TB`juJ00000NkvXXu0mjf!sjcP

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_discard.png b/Telegram/Resources/icons/call_discard.png
new file mode 100644
index 0000000000000000000000000000000000000000..312a8be7116e84bc4e1a1c52a31b961715336035
GIT binary patch
literal 405
zcmV;G0c!q<P)<h;3K|Lk000e1NJLTq000;O000mO1^@s63?#pm00048Nkl<ZIE~#@
zF^a-a6r3N0kisf;3L%1p*lCq6AP5@c5d@JSimk1cp2AZIqJ@oESO~!bSSVh=LL_3w
zV$n4QQP@qfJ22H}X5RmA_yPbCA*3kH|3^Ul)q$pID3wYu3<KG07Mi9Zkw^dl4u=DF
zyB*f+HRkg<Mxzn-`~Axh-YA4%p-|vtGNJGL{1m?Lb37i?G))R2_~>~LNT<`B&1M1N
zVzHp(ILzg8R8^IdBrz6?QB_su^LaL#O)i(qfMz<KGL=dN0~(D6Pp4C8vU<J#JWYtL
zRx6}C9*?ZmY6QS|zu!l<+x^iZpP?uUhQlF}$t0w9yZx)?kLYwdK($)s`FsvtbF<k{
z*Y&qgl*wec-EKpA&+}+m*4GYf+rEcfE*JKCJ<78D*8E0AQ8*Y3?#=T&kCjR#*doia
zxLU1PEEYdDex_~PbX}L_a{0UZx0I;x_y3|R{gPc17{5ai00000NkvXXu0mjfz5ums

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_discard@2x.png b/Telegram/Resources/icons/call_discard@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..8fffeb4128c24c6eca35f74eae29aa62c4b33d28
GIT binary patch
literal 736
zcmV<60w4W}P)<h;3K|Lk000e1NJLTq001xm001Be1^@s6RMh(%0007~Nkl<ZScUDE
zze{pa7{|Xy19Ql5Xb8+9BDhFhhcIbzYVa?}h@yX?#ZeFm?$FR2gwYVu5d8y9k%Kr0
zN<yJf4i!q}aQy+=_Z@oWMPIL7f7C7T3(t1WbH1PRJolV)1pwj~%Ve~HK{Wy}S|b3X
zH3BeNV}ndKo6V3U2_BCJ3kwUFpPz@*>BQ936ecGpVX;^M0JT~TrBVrnLIL;p_qe^i
zMJkm-JRV0TlfnD@`#=!vC2TeuH#RnSc6P>>mlqDIVzJ26(^IamuXAEzq9-qZ<MTm6
z2ztF<9v>gGTrLl6T1%>`^62P@9*<`@073}*{eH$`u`e^NrK_teuCA_92+@6Wn-uMG
zyWQB|--plV>)L;BZ*RE1zD6REz|GAKve_&iA0P4h`ifeu1^}>FEU??{n4X@-?CdOD
zE*F-Tmaww20=wPbHUH)1C3bdpkV>UA$FvYxtyb>s?Xl5lblstchX;DSUYgD3e)^UG
zXfm0&yu8eOKHqh%dc96XQ8+$6-o1rnS!O1a(N*F^BGI?;_DCj^y4Fmm({#C9odGsC
zH(9M#btN(ifF#T1G6R9YzxhH4Mj{d235*74DijJ)2*EK$Q4kCUH9wG_s&F{`aZ&ct
zHb1EX<VQQ1a=BbzJ3w|keZN(AcXu$Yt*xP0EcQG(7K;tdNI$urpPvy31ONbBTwG+K
zP|#I2RaLpWyQ_63_Z3#Fm5QRUQmN?j@bvUVNs?NRQ9Tgn=jWW6nHie#_Ha6#ytugN
z#eC~UrUjv@Dz~<_K7E%ABZLs#-rlCFs(Q?~0(64N=kt_h`OA&Bhb+r2PYez7{{sM^
zBuP9uIpN{qAsr6Kx0`Q<!{Ok;!2zSuDCg$pI#;Yo@muxl|A|KcMr#CMwEh5*r`7CK
S6KdE10000<MNUMnLSTZ+Dpzm-

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_record_active.png b/Telegram/Resources/icons/call_record_active.png
new file mode 100644
index 0000000000000000000000000000000000000000..4659bbb6c867209b1b34b08c808ba6e3e32dda8f
GIT binary patch
literal 412
zcmV;N0b~A&P)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0004FNkl<ZNQu={
zJC4FI6r325P@tRv(L#|XAmt7@Ln_XIL`^}76qG6zoPvfcq(jsZePUA(s@Vo2?Pl4S
zpOhWxNw)m_MsMsl1_0uN5T7irP0;Cd5JeG`Qjk(&wOYZlENv3jMHEH+i7*Un?_Hxl
zNhwt!$n#u_z__ve*&=^WF$|+to~IxA9_iZ%bkW=nnk`XL6xeRJ<z6XO$;q;;T<>-}
zoX=-{g2Uke$8peVwEzH{&8CvGUatWF?RFcE<6ytv-|eC@9JASsBpCz&0noB6hGED&
z&zWTzm&+wh(<A^+CKHn6bUM|aWZSlRJRT{f<Y+XyJEflI@pL*dO;dKe-N&qAJRVai
z#iA%UpU>Ir^~$q;zt6>D!OP`BDJ5Okea?%9!y%I-Dg8%E`OTM-B;jB%c+KpD5cGYY
zaU7S0<2a`8`xHXF$Lz}Aa2+lkle?k0UZjhMBzgw_U!pIGE>l2Piw}wb0000<MNUMn
GLSTYD(6{vf

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_record_active@2x.png b/Telegram/Resources/icons/call_record_active@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..8ce748f9c971094f756bdd99346b1de56d5630a7
GIT binary patch
literal 800
zcmV+*1K<3KP)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm0008#Nkl<ZXo2mR
z&r2#%6vvN`awZKb-H4*D#ig|9Z)nrXRkVws1WBQt(4Wwz{~$Lm{0CaNC@Rz$DKy&#
zL4hbjFpe$!zQubEPB-g1UY`zczHs3(_uO;N$1^`X*F{7i8|&A+6PQ*Gz`P|KK$d07
zX0xQKD(SjTx~`L=D3r-$NS0;M;fUzZLo8%OA`vti4UAL0UPm|_78dXC5aURe<!_T8
zg^(m^@c^>f?2pM0b$oohcmS%ZasyPWRbc?GA;q-Pb)6(h;<D}adgSqVM1&<_0KjB!
zTrQW0u=Dv2ObYv*oDZ25*}ej{2M{Tz0#;;u0FiPkU`4hE5GihekB<)mKmh3Q@Q_*W
z_xs#IHBDo#rPFD{|0KS+0ov`hF%JX+%zC|E=MMUnZ7>)#=B-wX+ZQ)Lv)MG}i9~`~
zzqq*I4w}#BnQNmypPrt$eGzds4yUK5Mnj@$8rIg<OzV;);o;$dtC?9^S~BhR`~B$m
z`-V*}mlNNS#p7{PA}1#&j7=yM!u|dI*yI&OK_C!d_T_RplU*zpn@vkuDwT{N-EJ45
zP>8XSBnjzs8kI@~nx;Y1G?dF_q*5s?FE2Cp(P$L!@9&1+%gf72+e&`pt*tE#1_LA5
z&CLx~R#q(cvtF+kl}g3%Hy8}Cxw$zz03yQq`MIgwrBVqS8ygcR@Avz0b#-O(b#``U
z<%gJTgDlIqy}dOBetv$&_VzZnkDZ+zyu7@ad|Y2&!|is@9)O78^ZB4C3iFWO-Q8h-
zfB(zVw`eqqgM$N9t5s%Sxm?Ej`nuIy(c1mHy1I&DvB)GdRJ+~A<KrWm%_cgX&RDxb
zp#ZPfYc+Z60K>7jw}-d4x3LM1)9dRic6WEDiY)|yh~V*fkV>UctJQu?Tvb&Z9UV>e
z_7V;-I>O;FlF1~Kcruxs#mTg|qs7m{r~mw-PU1f6|LF%7RSv+sWqW{`y@mdjY+nHf
eVBT^7=B?ka-YosA6<DtT0000<MNUMnLSTZLaD}k|

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_record_muted.png b/Telegram/Resources/icons/call_record_muted.png
new file mode 100644
index 0000000000000000000000000000000000000000..0747c8afa4b5c00fcfff75f64be02c363e4bc195
GIT binary patch
literal 459
zcmV;+0W|)JP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0004!Nkl<ZNQuqW
zJ&uAv6bJApYH*103?4vR?M*B>gSW7-_XH3s4&X;cs6_Ar7J3L9E11y&tN&KRF0hNB
znCwfYm>KeZ$$M`?gNQ&M4CRx_O9P!whb+q?Ap~*G$uvz;Rkbt%xu(-89LE7;3_s5;
z%R)8`!$^Hou)a5r;{X6W&r2afh#U|_QFVYZ2G8?y$ZEC90RXB4e<96g6YKT59w3YC
z_j@qL&}y}E$jd+xve|6v0g90Ac3Tf15gA$JWgw1Y>~_0k&pA&J-}jTo%lpIOfcyPk
z9yp)RxL&VG64NwO&6Nldq1|pH3_~1`$MV2pu>b(fW-|~GR8@sx7>J?>kH;g0$myET
z=jDN3uZPR!0?s)`qfxFWzVGMCrD<BVCXdHs2q6&1F_z0E`u%=#HyjQThGDK;m8WPh
z7~pg|CAH_Ary72{-2ebV5ELQBW2PtybX|vS+eu>EwxR1fy4@~<Ab5t9nq?5tuP<2B
zv}eeBKo)Ubw|+n(uIpklnWWAeQu$Q;kDvZK@dYkG0lOkgLDv8P002ovPDHLkV1n?I
B#!UbK

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_record_muted@2x.png b/Telegram/Resources/icons/call_record_muted@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..4d7d690e989ea96d3a5ff9be8903282a223d16a8
GIT binary patch
literal 843
zcmV-R1GM~!P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm0009LNkl<ZXo2n7
z%}VP)6bJC*U6?3RV^{5{UB;qI-S`SVfbLufE|dzDF5HPP;Ul>91q5ej7crGa>&MPb
zRVso=3GoB}cX4lmF=>;SNz;M{28Pa@=KRRagtS9M1RCJ@lXd}d;}?*22nWcrOqEK7
zYPA|!mPM9jk*@2sy}eD6B#FKp5&hf5LRz6vK%>z>r#L@9$JEr6u<QMAod+3X;G9EM
z)xa;3Ww~qdb{K{MNs^KWFvf6ma{~ZqHk*NnN~IE5ydC9oIe7r*+;!0)M6Ff}4bU_#
zd4Q^_Xf~Un5te0z2AHPVn>`Q(p=nxZ0D#^Aq97DSiG=7GASpyo0ZAZw4iJq<1t1cU
z8bA~xRe%UY>VUY2z5v8Q^bNp9e13jLLi81Yi16|8;aTV15IqOHy}dbwZEkLQ#@p?-
z?`h+Q$Ye5}{H?7mC+GeBJv1QnE9KMElhe=Ta-Q)<qv4y!7$eR(sjBLH_VV)LnU~M!
zo&MwFW7x97zY||wT{->v`FYRy!NEbtTtCG3SXfwa`j?lNVap<-khf}YZ_l~KrfFh)
zeB3oYJUongz3#h=x-BOnLN=Sl>+7pCZ+CZB{2rK_n{ySjv$NxwGdVd4!!RNv6h(oq
z>&~2Fv6##$^z}9K`T5!R7Y0d^P%f9DX&Ow^glU?d4ep1Sn3%xn=_!tmj*>ZrhzN^|
zi>~v&TCHMaWF#=JSS;fH{yx;5n4O(X4nRcM-`{r?a(sLYMNxzye$b*N$g+%TwdyM5
z_VyM_OG{}Xq9<juS=Xks<K*N7Yiny@jCpbxV_08b$Hm1(sEwT{fQT?QHipB)Lr*bw
zv|23~hJkv$j#jJHah?D~to4W0)m1z^JOmc#SFoVv6c0c|7#$r&sZ_$**;!!Wx~^km
zV*|1*LseDW-QC4S#0PwB)6>&fUS9STzOu67``i$K5DT!|_vkivW@aW1B7HziM1KHm
z#1A%h{{T86IOl@{AaXykudlC>&*ujVU?Vu^$mMcf=MCA4L5<)4iPMf>K-w|zfWMJu
Vkz32R+aCY`002ovPDHLkV1k!LlCJ;&

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_shadow_left.png b/Telegram/Resources/icons/call_shadow_left.png
new file mode 100644
index 0000000000000000000000000000000000000000..74864ad4b58577ca3a3d1856559ea719998b84c5
GIT binary patch
literal 100
zcmeAS@N?(olHy`uVBq!ia0vp^5<twz!3HFctjk*sq_jO<978x}=5`r!F(?QemhyV}
zKYq>S&IOG=oClave$R06yz;H^)?a3(FUic47BRliXODFQYGd$p^>bP0l+XkK{<R)!

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_shadow_left@2x.png b/Telegram/Resources/icons/call_shadow_left@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..6a0e6e4c537f3e8ab7f3005d58f347ea1afbd90f
GIT binary patch
literal 125
zcmeAS@N?(olHy`uVBq!ia0vp^20+Zj!3HGdHpD9eDHl%{#}J9BXM3&r8XQEJ+Rxwn
ze<JVurdye+XD0|a9AH)8^klf<cFBNqTYIjjlhCb8)3#pwVQgLPnYVjeW!?U%_FL<g
Z@XlJt_-eZ>=Q^Ny44$rjF6*2UngGhbD!Kpw

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_shadow_top.png b/Telegram/Resources/icons/call_shadow_top.png
new file mode 100644
index 0000000000000000000000000000000000000000..653e0afa994e839c76ca58e1a20333f70799a448
GIT binary patch
literal 103
zcmeAS@N?(olHy`uVBq!ia0vp^j6f{G!3HF)&rH7sr1U&p978x}=AL%sVld!1a-g^S
z$A9tb-4}xPoY534U<$ZVck;}_Rb_df_8w5kT*`Q0HIwMytuLj4IvG4&{an^LB{Ts5
D!GI#Y

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_shadow_top@2x.png b/Telegram/Resources/icons/call_shadow_top@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..47c672ca3b028dbf40d89d5aa5825b95344fc3d6
GIT binary patch
literal 127
zcmeAS@N?(olHy`uVBq!ia0vp^Oh9bF!3HF)SyndzDK}3S#}J9BdnXw3F(_~_dpG{K
zj}$-JG121Wl{=0c91Uy?M>K>d+UmF(eVyYHI@zjormCo72uu0?l}USU$Sv8CG->+w
bKRmJqnM^Htf2OejO=R$N^>bP0l+XkK@!lpq

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_shadow_top_left.png b/Telegram/Resources/icons/call_shadow_top_left.png
new file mode 100644
index 0000000000000000000000000000000000000000..baba49364f09da2655e03782fd31be9da4594658
GIT binary patch
literal 295
zcmV+?0oeYDP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0002)Nkl<ZNQtGC
z;SPi_2!l76@Bhf>uNtfcakpfN-YxxTVa_qeZ)~hNXY1VB5$cMUfGE;=&cG6YgDi$!
zop}W)fof;M5>SbnnS9UOJOG(EgxvjlXIOOe$pOU7Y`FXFS-E1J3!s|G!fUPJ?l%y-
zQN=D6=6E2E4M+r4G07=H<?en7AII^4#JE}j<sd=;!f$|7eMlpUJ%C_z5dmssTTPBJ
zFo=7RPEjV#pcqw*NC9LI)J5ZQj8y}|5xq89xltEwYl_%GbvI(=Z3kaK-EWbxAV<Y8
tRY<k0{5Ns-WtD*4s@s5ngtq`Y>jc4T2IpqmK|lZi002ovPDHLkV1fWgeZv3%

literal 0
HcmV?d00001

diff --git a/Telegram/Resources/icons/call_shadow_top_left@2x.png b/Telegram/Resources/icons/call_shadow_top_left@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d9672fb51b6dd970b3413917c45f15c5453534d
GIT binary patch
literal 559
zcmV+~0?_@5P)<h;3K|Lk000e1NJLTq001xm001xu1^@s6R|5Hm0005@Nkl<ZXo2mT
z;f{ka41){o{a^X+7gCAh*hvH2q_b4Px>Ec#O%U3)F~-mMV(o7mSk>o^n{M0IHrH(c
zt0vX!+W^=NJ6C<y5<q0C`dxNh2>?y(_1ivWOh##dtVwi^=2E_!B?DxP+oO73kp#e2
z4jHciMSY$qfU-TjqteIbZvjxuN9PQR8D~Ht01G)XjLfSr89{)^JTg7T82i3UFbG8P
zBLOH-s6`Mez)}*kL{&`BpAnFS$QTa+keGRPWYK5`U`c3Ao^AfDM57fzNv6m=1F-<!
z0<i@R0qkJ5caixB>b%Y~@0pCiN;%*q?A(syxSU5@N&$#qXbwax?`@;g{0xD%R!1IO
z09Y8K>f=R#K~y3T0(e5nX19+8IgX<h0QHASY#%TRh-cdNDdkjE`h6fuUK9y63h16G
z05p-e>L?0cn?YD5$|(ILMtzm%!FinLi+rR2qKl$LR$*)<WA~&0cAwjGWea(w6_wk^
z$|!?F=DlcmV^_>m0G7}e!h?!T-%^DT1T)CYD-ac>--)9!tSJl727nAJhE$`dFQc|!
zXjsIe)n18!44|4q$*s&)(JjA`Rn1g%0f@?`RmP-J1gdf)@Z<`vDz%0FB3Nq<Fu*$&
xpah^Ac_L!BJO`dsdl`Ki<m>+{@elB}egPU73!TJ&h#UX_002ovPDHLkV1h57@K68%

literal 0
HcmV?d00001

diff --git a/Telegram/SourceFiles/base/openssl_help.h b/Telegram/SourceFiles/base/openssl_help.h
new file mode 100644
index 000000000..90639e241
--- /dev/null
+++ b/Telegram/SourceFiles/base/openssl_help.h
@@ -0,0 +1,146 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include <openssl/bn.h>
+#include <openssl/sha.h>
+
+namespace openssl {
+
+class Context {
+public:
+	Context() : _data(BN_CTX_new()) {
+	}
+	Context(const Context &other) = delete;
+	Context(Context &&other) : _data(base::take(other._data)) {
+	}
+	Context &operator=(const Context &other) = delete;
+	Context &operator=(Context &&other) {
+		_data = base::take(other._data);
+		return *this;
+	}
+	~Context() {
+		if (_data) {
+			BN_CTX_free(_data);
+		}
+	}
+
+	BN_CTX *raw() const {
+		return _data;
+	}
+
+private:
+	BN_CTX *_data = nullptr;
+
+};
+
+class BigNum {
+public:
+	BigNum() {
+		BN_init(raw());
+	}
+	BigNum(const BigNum &other) : BigNum() {
+		*this = other;
+	}
+	BigNum &operator=(const BigNum &other) {
+		if (other.failed() || !BN_copy(raw(), other.raw())) {
+			_failed = true;
+		}
+		return *this;
+	}
+	~BigNum() {
+		BN_clear_free(raw());
+	}
+
+	explicit BigNum(unsigned int word) : BigNum() {
+		setWord(word);
+	}
+	explicit BigNum(base::const_byte_span bytes) : BigNum() {
+		setBytes(bytes);
+	}
+
+	void setWord(unsigned int word) {
+		if (!BN_set_word(raw(), word)) {
+			_failed = true;
+		}
+	}
+	void setBytes(base::const_byte_span bytes) {
+		if (!BN_bin2bn(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size(), raw())) {
+			_failed = true;
+		}
+	}
+	void setModExp(const BigNum &a, const BigNum &p, const BigNum &m, const Context &context = Context()) {
+		if (a.failed() || p.failed() || m.failed()) {
+			_failed = true;
+		} else if (a.isNegative() || p.isNegative() || m.isNegative()) {
+			_failed = true;
+		} else if (!BN_mod_exp(raw(), a.raw(), p.raw(), m.raw(), context.raw())) {
+			_failed = true;
+		} else if (isNegative()) {
+			_failed = true;
+		}
+	}
+
+	bool isNegative() const {
+		return BN_is_negative(raw());
+	}
+
+	std::vector<gsl::byte> getBytes() const {
+		if (failed()) {
+			return std::vector<gsl::byte>();
+		}
+		auto length = BN_num_bytes(raw());
+		auto result = std::vector<gsl::byte>(length, gsl::byte());
+		auto resultSize = BN_bn2bin(raw(), reinterpret_cast<unsigned char*>(result.data()));
+		t_assert(resultSize == length);
+		return result;
+	}
+
+	BIGNUM *raw() {
+		return &_data;
+	}
+	const BIGNUM *raw() const {
+		return &_data;
+	}
+
+	bool failed() const {
+		return _failed;
+	}
+
+private:
+	BIGNUM _data;
+	bool _failed = false;
+
+};
+
+inline std::array<gsl::byte, SHA256_DIGEST_LENGTH> Sha256(base::const_byte_span bytes) {
+	auto result = std::array<gsl::byte, SHA256_DIGEST_LENGTH>();
+	SHA256(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size(), reinterpret_cast<unsigned char*>(result.data()));
+	return result;
+}
+
+inline std::array<gsl::byte, SHA_DIGEST_LENGTH> Sha1(base::const_byte_span bytes) {
+	auto result = std::array<gsl::byte, SHA_DIGEST_LENGTH>();
+	SHA1(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size(), reinterpret_cast<unsigned char*>(result.data()));
+	return result;
+}
+
+} // namespace openssl
diff --git a/Telegram/SourceFiles/base/weak_unique_ptr.h b/Telegram/SourceFiles/base/weak_unique_ptr.h
index ce0b7b8ce..b4dbac92d 100644
--- a/Telegram/SourceFiles/base/weak_unique_ptr.h
+++ b/Telegram/SourceFiles/base/weak_unique_ptr.h
@@ -24,7 +24,7 @@ namespace base {
 
 class enable_weak_from_this;
 
-template <typename T, typename = std::enable_if_t<std::is_base_of<enable_weak_from_this, T>::value>>
+template <typename T>
 class weak_unique_ptr;
 
 class enable_weak_from_this {
@@ -42,12 +42,12 @@ public:
 	}
 
 private:
-	template <typename Child, typename>
+	template <typename Child>
 	friend class weak_unique_ptr;
 
 	std::shared_ptr<enable_weak_from_this*> getGuarded() {
 		if (!_guarded) {
-			_guarded = std::make_shared<enable_weak_from_this*>(this);
+			_guarded = std::make_shared<enable_weak_from_this*>(static_cast<enable_weak_from_this*>(this));
 		}
 		return _guarded;
 	}
@@ -56,7 +56,7 @@ private:
 
 };
 
-template <typename T, typename>
+template <typename T>
 class weak_unique_ptr {
 public:
 	weak_unique_ptr() = default;
diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style
new file mode 100644
index 000000000..46f389802
--- /dev/null
+++ b/Telegram/SourceFiles/calls/calls.style
@@ -0,0 +1,80 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+using "basic.style";
+
+using "ui/widgets/widgets.style";
+
+callWidth: 300px;
+callHeight: 470px;
+callShadow: Shadow {
+	left: icon {{ "call_shadow_left", windowShadowFg }};
+	topLeft: icon {{ "call_shadow_top_left", windowShadowFg }};
+	top: icon {{ "call_shadow_top", windowShadowFg }};
+	topRight: icon {{ "call_shadow_top_left-flip_horizontal", windowShadowFg }};
+	right: icon {{ "call_shadow_left-flip_horizontal", windowShadowFg }};
+	bottomRight: icon {{ "call_shadow_top_left-flip_vertical-flip_horizontal", windowShadowFg }};
+	bottom: icon {{ "call_shadow_top-flip_vertical", windowShadowFg }};
+	bottomLeft: icon {{ "call_shadow_top_left-flip_vertical", windowShadowFg }};
+	extend: margins(9px, 8px, 9px, 10px);
+	fallback: windowShadowFgFallback;
+}
+
+callButton: IconButton {
+	width: 64px;
+	height: 64px;
+
+	iconPosition: point(20px, 20px);
+
+	rippleAreaPosition: point(8px, 8px);
+	rippleAreaSize: 48px;
+	ripple: defaultRippleAnimation;
+}
+
+callAnswer: CallButton {
+	button: IconButton(callButton) {
+		icon: icon {{ "call_answer", callIconFg }};
+		ripple: RippleAnimation(defaultRippleAnimation) {
+			color: callAnswerRipple;
+		}
+	}
+	bg: callAnswerBg;
+}
+callHangup: CallButton {
+	button: IconButton(callButton) {
+		icon: icon {{ "call_discard", callIconFg }};
+		iconPosition: point(20px, 24px);
+		ripple: RippleAnimation(defaultRippleAnimation) {
+			color: callHangupRipple;
+		}
+	}
+	bg: callHangupBg;
+}
+callMuteToggle: IconButton(callButton) {
+	icon: icon {{ "call_record_active", callIconFg }};
+	ripple: RippleAnimation(defaultRippleAnimation) {
+		color: callMuteRipple;
+	}
+}
+callUnmuteIcon: icon {{ "call_record_muted", callIconFg }};
+
+callControlsTop: 84px;
+callControlsSkip: 8px;
+callMuteRight: 12px;
diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp
index 745ddfbc0..42a5e719f 100644
--- a/Telegram/SourceFiles/calls/calls_call.cpp
+++ b/Telegram/SourceFiles/calls/calls_call.cpp
@@ -21,9 +21,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "calls/calls_call.h"
 
 #include "auth_session.h"
+#include "mainwidget.h"
 #include "calls/calls_instance.h"
+#include "base/openssl_help.h"
 
-#include <openssl/bn.h>
 #include <openssl/rand.h>
 #include <openssl/sha.h>
 
@@ -45,86 +46,195 @@ namespace {
 
 constexpr auto kMinLayer = 65;
 constexpr auto kMaxLayer = 65; // MTP::CurrentLayer?
+constexpr auto kHangupTimeoutMs = 5000; // TODO read from server config
 
 using tgvoip::Endpoint;
 
 void ConvertEndpoint(std::vector<tgvoip::Endpoint> &ep, const MTPDphoneConnection &mtc) {
-	if (mtc.vpeer_tag.v.length() != 16)
+	if (mtc.vpeer_tag.v.length() != 16) {
 		return;
+	}
 	auto ipv4 = tgvoip::IPv4Address(std::string(mtc.vip.v.constData(), mtc.vip.v.size()));
 	auto ipv6 = tgvoip::IPv6Address(std::string(mtc.vipv6.v.constData(), mtc.vipv6.v.size()));
 	ep.push_back(Endpoint((int64_t)mtc.vid.v, (uint16_t)mtc.vport.v, ipv4, ipv6, EP_TYPE_UDP_RELAY, (unsigned char*)mtc.vpeer_tag.v.data()));
 }
 
+std::vector<gsl::byte> ComputeModExp(const DhConfig &config, const openssl::BigNum &base, const std::array<gsl::byte, Call::kRandomPowerSize> &randomPower) {
+	using namespace openssl;
+
+	BigNum resultBN;
+	resultBN.setModExp(base, BigNum(randomPower), BigNum(config.p));
+	auto result = resultBN.getBytes();
+	constexpr auto kMaxModExpSize = 256;
+	t_assert(result.size() <= kMaxModExpSize);
+	return result;
+}
+
+std::vector<gsl::byte> ComputeModExpFirst(const DhConfig &config, const std::array<gsl::byte, Call::kRandomPowerSize> &randomPower) {
+	return ComputeModExp(config, openssl::BigNum(config.g), randomPower);
+}
+
+std::vector<gsl::byte> ComputeModExpFinal(const DhConfig &config, base::const_byte_span first, const std::array<gsl::byte, Call::kRandomPowerSize> &randomPower) {
+	return ComputeModExp(config, openssl::BigNum(first), randomPower);
+}
+
+constexpr auto kFingerprintDataSize = 256;
+uint64 ComputeFingerprint(const std::array<gsl::byte, kFingerprintDataSize> &authKey) {
+	auto hash = openssl::Sha1(authKey);
+	return (gsl::to_integer<uint64>(hash[19]) << 56)
+		| (gsl::to_integer<uint64>(hash[18]) << 48)
+		| (gsl::to_integer<uint64>(hash[17]) << 40)
+		| (gsl::to_integer<uint64>(hash[16]) << 32)
+		| (gsl::to_integer<uint64>(hash[15]) << 24)
+		| (gsl::to_integer<uint64>(hash[14]) << 16)
+		| (gsl::to_integer<uint64>(hash[13]) << 8)
+		| (gsl::to_integer<uint64>(hash[12]));
+}
+
 } // namespace
 
-Call::Call(gsl::not_null<Delegate*> delegate, gsl::not_null<UserData*> user)
+Call::Call(gsl::not_null<Delegate*> delegate, gsl::not_null<UserData*> user, Type type)
 : _delegate(delegate)
-, _user(user) {
+, _user(user)
+, _type(type) {
+	if (_type == Type::Outgoing) {
+		setState(State::Requesting);
+	}
+}
+
+void Call::generateRandomPower(base::const_byte_span random) {
+	Expects(random.size() == _randomPower.size());
+	memset_rand(_randomPower.data(), _randomPower.size());
+	for (auto i = 0, count = int(_randomPower.size()); i != count; i++) {
+		_randomPower[i] ^= random[i];
+	}
+}
+
+void Call::start(base::const_byte_span random) {
 	// Save config here, because it is possible that it changes between
 	// different usages inside the same call.
 	_dhConfig = _delegate->getDhConfig();
-}
+	t_assert(_dhConfig.g != 0);
+	t_assert(!_dhConfig.p.empty());
 
-void Call::generateSalt(base::const_byte_span random) {
-	Expects(random.size() == _salt.size());
-	memset_rand(_salt.data(), _salt.size());
-	for (auto i = 0, count = int(_salt.size()); i != count; i++) {
-		_salt[i] ^= random[i];
+	generateRandomPower(random);
+
+	if (_type == Type::Outgoing) {
+		startOutgoing();
+	} else {
+		startIncoming();
 	}
 }
 
-void Call::startOutgoing(base::const_byte_span random) {
-	generateSalt(random);
-
-	BN_CTX* ctx = BN_CTX_new();
-	BN_CTX_init(ctx);
-	BIGNUM i_g_a;
-	BN_init(&i_g_a);
-	BN_set_word(&i_g_a, _dhConfig.g);
-	BIGNUM tmp;
-	BN_init(&tmp);
-	BIGNUM saltBN;
-	BN_init(&saltBN);
-	BN_bin2bn(reinterpret_cast<const unsigned char*>(_salt.data()), _salt.size(), &saltBN);
-	BIGNUM pbytesBN;
-	BN_init(&pbytesBN);
-	BN_bin2bn(reinterpret_cast<const unsigned char*>(_dhConfig.p.data()), _dhConfig.p.size(), &pbytesBN);
-	BN_mod_exp(&tmp, &i_g_a, &saltBN, &pbytesBN, ctx);
-	auto g_a_length = BN_num_bytes(&tmp);
-	_g_a = std::vector<gsl::byte>(g_a_length, gsl::byte());
-	BN_bn2bin(&tmp, reinterpret_cast<unsigned char*>(_g_a.data()));
-	constexpr auto kMaxGASize = 256;
-	if (_g_a.size() > kMaxGASize) {
-		auto slice = gsl::make_span(_g_a).subspan(1, kMaxGASize);
-		_g_a = std::vector<gsl::byte>(slice.begin(), slice.end());
+void Call::startOutgoing() {
+	_ga = ComputeModExpFirst(_dhConfig, _randomPower);
+	if (_ga.empty()) {
+		LOG(("Call Error: Could not compute mod-exp first."));
+		setState(State::Failed);
+		return;
 	}
-	BN_CTX_free(ctx);
-
+	_gaHash = openssl::Sha256(_ga);
 	auto randomID = rand_value<int32>();
-	auto g_a_hash = std::array<gsl::byte, SHA256_DIGEST_LENGTH>();
-	SHA256(reinterpret_cast<const unsigned char*>(_g_a.data()), _g_a.size(), reinterpret_cast<unsigned char*>(g_a_hash.data()));
 
-	request(MTPphone_RequestCall(_user->inputUser, MTP_int(randomID), MTP_bytes(g_a_hash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
+	setState(State::Requesting);
+	request(MTPphone_RequestCall(_user->inputUser, MTP_int(randomID), MTP_bytes(_gaHash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
 		Expects(result.type() == mtpc_phone_phoneCall);
 		auto &call = result.c_phone_phoneCall();
 		App::feedUsers(call.vusers);
 		if (call.vphone_call.type() != mtpc_phoneCallWaiting) {
-			LOG(("API Error: Expected phoneCallWaiting in response to phone.requestCall()"));
-			failed();
+			LOG(("Call Error: Expected phoneCallWaiting in response to phone.requestCall()"));
+			setState(State::Failed);
 			return;
 		}
+
+		setState(State::Waiting);
+		if (_finishAfterRequestingCall) {
+			hangup();
+			return;
+		}
+
 		auto &phoneCall = call.vphone_call.c_phoneCallWaiting();
 		_id = phoneCall.vid.v;
 		_accessHash = phoneCall.vaccess_hash.v;
+		handleUpdate(call.vphone_call);
 	}).fail([this](const RPCError &error) {
-		failed();
+		setState(State::Failed);
 	}).send();
 }
 
+void Call::startIncoming() {
+	setState(State::Ringing);
+}
+
+void Call::answer() {
+	Expects(_type == Type::Incoming);
+	_gb = ComputeModExpFirst(_dhConfig, _randomPower);
+	if (_gb.empty()) {
+		LOG(("Call Error: Could not compute mod-exp first."));
+		setState(State::Failed);
+		return;
+	}
+
+	setState(State::ExchangingKeys);
+	request(MTPphone_AcceptCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_gb), _protocol)).done([this](const MTPphone_PhoneCall &result) {
+		Expects(result.type() == mtpc_phone_phoneCall);
+		auto &call = result.c_phone_phoneCall();
+		App::feedUsers(call.vusers);
+		if (call.vphone_call.type() != mtpc_phoneCallWaiting) {
+			LOG(("Call Error: Expected phoneCallWaiting in response to phone.acceptCall()"));
+			setState(State::Failed);
+			return;
+		}
+
+		handleUpdate(call.vphone_call);
+	}).fail([this](const RPCError &error) {
+		setState(State::Failed);
+	}).send();
+}
+
+void Call::setMute(bool mute) {
+	_mute = mute;
+	if (_controller) {
+		_controller->SetMicMute(_mute);
+	}
+}
+
+void Call::hangup() {
+	auto missed = (_state == State::Ringing || (_state == State::Waiting && _type == Type::Outgoing));
+	auto reason = missed ? MTP_phoneCallDiscardReasonMissed() : MTP_phoneCallDiscardReasonHangup();
+	finish(reason);
+}
+
+void Call::decline() {
+	finish(MTP_phoneCallDiscardReasonBusy());
+}
+
 bool Call::handleUpdate(const MTPPhoneCall &call) {
 	switch (call.type()) {
-	case mtpc_phoneCallRequested: Unexpected("phoneCallRequested call inside an existing call handleUpdate()");
+	case mtpc_phoneCallRequested: {
+		auto &data = call.c_phoneCallRequested();
+		if (_type != Type::Incoming
+			|| _id != 0
+			|| peerToUser(_user->id) != data.vadmin_id.v) {
+			Unexpected("phoneCallRequested call inside an existing call handleUpdate()");
+		}
+		if (AuthSession::CurrentUserId() != data.vparticipant_id.v) {
+			LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(data.vparticipant_id.v).arg(AuthSession::CurrentUserId()));
+			setState(State::Failed);
+			return true;
+
+		}
+		_id = data.vid.v;
+		_accessHash = data.vaccess_hash.v;
+		_protocol = data.vprotocol;
+		auto gaHashBytes = bytesFromMTP(data.vg_a_hash);
+		if (gaHashBytes.size() != _gaHash.size()) {
+			LOG(("Call Error: Wrong g_a_hash size %1, expected %2.").arg(gaHashBytes.size()).arg(_gaHash.size()));
+			setState(State::Failed);
+			return true;
+		}
+		base::copy_bytes(gsl::make_span(_gaHash), gaHashBytes);
+	} return true;
 
 	case mtpc_phoneCallEmpty: {
 		auto &data = call.c_phoneCallEmpty();
@@ -132,7 +242,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 			return false;
 		}
 		LOG(("Call Error: phoneCallEmpty received."));
-		failed();
+		setState(State::Failed);
 	} return true;
 
 	case mtpc_phoneCallWaiting: {
@@ -147,6 +257,9 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 		if (data.vid.v != _id) {
 			return false;
 		}
+		if (_type == Type::Incoming && _state == State::ExchangingKeys) {
+			startConfirmedCall(data);
+		}
 	} return true;
 
 	case mtpc_phoneCallDiscarded: {
@@ -154,7 +267,17 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 		if (data.vid.v != _id) {
 			return false;
 		}
-		_delegate->callFinished(this, data.vreason);
+		if (data.is_need_debug()) {
+			auto debugLog = _controller ? _controller->GetDebugLog() : std::string();
+			if (!debugLog.empty()) {
+				MTP::send(MTPphone_SaveCallDebug(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_dataJSON(MTP_string(debugLog))));
+			}
+		}
+		if (data.has_reason() && data.vreason.type() == mtpc_phoneCallDiscardReasonBusy) {
+			setState(State::Busy);
+		} else {
+			setState(State::Ended);
+		}
 	} return true;
 
 	case mtpc_phoneCallAccepted: {
@@ -162,7 +285,10 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 		if (data.vid.v != _id) {
 			return false;
 		}
-		if (checkCallFields(data)) {
+		if (_type != Type::Outgoing) {
+			LOG(("Call Error: Unexpected phoneCallAccepted for an incoming call."));
+			setState(State::Failed);
+		} else if (checkCallFields(data)) {
 			confirmAcceptedCall(data);
 		}
 	} return true;
@@ -172,69 +298,83 @@ bool Call::handleUpdate(const MTPPhoneCall &call) {
 }
 
 void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) {
+	Expects(_type == Type::Outgoing);
+
 	// TODO check isGoodGaAndGb
-
-	BN_CTX *ctx = BN_CTX_new();
-	BN_CTX_init(ctx);
-	BIGNUM p;
-	BIGNUM i_authKey;
-	BIGNUM res;
-	BIGNUM salt;
-	BN_init(&p);
-	BN_init(&i_authKey);
-	BN_init(&res);
-	BN_init(&salt);
-	BN_bin2bn(reinterpret_cast<const unsigned char*>(_dhConfig.p.data()), _dhConfig.p.size(), &p);
-	BN_bin2bn(reinterpret_cast<const unsigned char*>(call.vg_b.v.constData()), call.vg_b.v.length(), &i_authKey);
-	BN_bin2bn(reinterpret_cast<const unsigned char*>(_salt.data()), _salt.size(), &salt);
-
-	BN_mod_exp(&res, &i_authKey, &salt, &p, ctx);
-	BN_CTX_free(ctx);
-	auto realAuthKeyLength = BN_num_bytes(&res);
-	auto realAuthKeyBytes = QByteArray(realAuthKeyLength, Qt::Uninitialized);
-	BN_bn2bin(&res, reinterpret_cast<unsigned char*>(realAuthKeyBytes.data()));
-
-	if (realAuthKeyLength > kAuthKeySize) {
-		memcpy(_authKey.data(), realAuthKeyBytes.constData() + (realAuthKeyLength - kAuthKeySize), kAuthKeySize);
-	} else if (realAuthKeyLength < kAuthKeySize) {
-		memset(_authKey.data(), 0, kAuthKeySize - realAuthKeyLength);
-		memcpy(_authKey.data() + (kAuthKeySize - realAuthKeyLength), realAuthKeyBytes.constData(), realAuthKeyLength);
-	} else {
-		memcpy(_authKey.data(), realAuthKeyBytes.constData(), kAuthKeySize);
+	auto computedAuthKey = ComputeModExpFinal(_dhConfig, byteVectorFromMTP(call.vg_b), _randomPower);
+	if (computedAuthKey.empty()) {
+		LOG(("Call Error: Could not compute mod-exp final."));
+		setState(State::Failed);
+		return;
 	}
 
-	unsigned char authKeyHash[SHA_DIGEST_LENGTH];
-	SHA1(reinterpret_cast<const unsigned char*>(_authKey.data()), _authKey.size(), authKeyHash);
+	auto computedAuthKeySize = computedAuthKey.size();
+	t_assert(computedAuthKeySize <= kAuthKeySize);
+	auto authKeyBytes = gsl::make_span(_authKey);
+	if (computedAuthKeySize < kAuthKeySize) {
+		base::set_bytes(authKeyBytes.subspan(0, kAuthKeySize - computedAuthKeySize), gsl::byte());
+		base::copy_bytes(authKeyBytes.subspan(kAuthKeySize - computedAuthKeySize), computedAuthKey);
+	} else {
+		base::copy_bytes(authKeyBytes, computedAuthKey);
+	}
+	_keyFingerprint = ComputeFingerprint(_authKey);
 
-	_keyFingerprint = ((uint64)authKeyHash[19] << 56)
-		| ((uint64)authKeyHash[18] << 48)
-		| ((uint64)authKeyHash[17] << 40)
-		| ((uint64)authKeyHash[16] << 32)
-		| ((uint64)authKeyHash[15] << 24)
-		| ((uint64)authKeyHash[14] << 16)
-		| ((uint64)authKeyHash[13] << 8)
-		| ((uint64)authKeyHash[12]);
-
-	request(MTPphone_ConfirmCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_g_a), MTP_long(_keyFingerprint), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
+	setState(State::ExchangingKeys);
+	request(MTPphone_ConfirmCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_ga), MTP_long(_keyFingerprint), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) {
 		Expects(result.type() == mtpc_phone_phoneCall);
 		auto &call = result.c_phone_phoneCall();
 		App::feedUsers(call.vusers);
 		if (call.vphone_call.type() != mtpc_phoneCall) {
-			LOG(("API Error: Expected phoneCall in response to phone.confirmCall()"));
-			failed();
+			LOG(("Call Error: Expected phoneCall in response to phone.confirmCall()"));
+			setState(State::Failed);
 			return;
 		}
+
 		createAndStartController(call.vphone_call.c_phoneCall());
 	}).fail([this](const RPCError &error) {
-		failed();
+		setState(State::Failed);
 	}).send();
 }
 
+void Call::startConfirmedCall(const MTPDphoneCall &call) {
+	Expects(_type == Type::Incoming);
+
+	auto firstBytes = bytesFromMTP(call.vg_a_or_b);
+	if (_gaHash != openssl::Sha256(firstBytes)) {
+		LOG(("Call Error: Wrong g_a hash received."));
+		setState(State::Failed);
+		return;
+	}
+
+	// TODO check isGoodGaAndGb
+	auto computedAuthKey = ComputeModExpFinal(_dhConfig, firstBytes, _randomPower);
+	if (computedAuthKey.empty()) {
+		LOG(("Call Error: Could not compute mod-exp final."));
+		setState(State::Failed);
+		return;
+	}
+
+	auto computedAuthKeySize = computedAuthKey.size();
+	t_assert(computedAuthKeySize <= kAuthKeySize);
+	auto authKeyBytes = gsl::make_span(_authKey);
+	if (computedAuthKeySize < kAuthKeySize) {
+		base::set_bytes(authKeyBytes.subspan(0, kAuthKeySize - computedAuthKeySize), gsl::byte());
+		base::copy_bytes(authKeyBytes.subspan(kAuthKeySize - computedAuthKeySize), computedAuthKey);
+	} else {
+		base::copy_bytes(authKeyBytes, computedAuthKey);
+	}
+	_keyFingerprint = ComputeFingerprint(_authKey);
+
+	createAndStartController(call);
+}
+
 void Call::createAndStartController(const MTPDphoneCall &call) {
 	if (!checkCallFields(call)) {
 		return;
 	}
 
+	setState(State::Established);
+
 	voip_config_t config;
 	config.data_saving = DATA_SAVING_NEVER;
 	config.enableAEC = true;
@@ -250,10 +390,13 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
 	}
 
 	_controller = std::make_unique<tgvoip::VoIPController>();
+	if (_mute) {
+		_controller->SetMicMute(_mute);
+	}
 	_controller->implData = static_cast<void*>(this);
 	_controller->SetRemoteEndpoints(endpoints, true);
 	_controller->SetConfig(&config);
-	_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), true);
+	_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), (_type == Type::Outgoing));
 	_controller->SetStateCallback([](tgvoip::VoIPController *controller, int state) {
 		static_cast<Call*>(controller->implData)->handleControllerStateChange(controller, state);
 	});
@@ -263,47 +406,52 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
 
 void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int state) {
 	// NB! Can be called from an arbitrary thread!
-	Expects(controller == _controller.get());
+	// Expects(controller == _controller.get()); This can be called from ~VoIPController()!
 	Expects(controller->implData == static_cast<void*>(this));
 
 	switch (state) {
 	case STATE_WAIT_INIT: {
-		DEBUG_LOG(("Call Info: State changed to Established."));
+		DEBUG_LOG(("Call Info: State changed to WaitingInit."));
+		setStateQueued(State::WaitingInit);
 	} break;
 
 	case STATE_WAIT_INIT_ACK: {
-		DEBUG_LOG(("Call Info: State changed to Established."));
+		DEBUG_LOG(("Call Info: State changed to WaitingInitAck."));
+		setStateQueued(State::WaitingInitAck);
 	} break;
 
 	case STATE_ESTABLISHED: {
 		DEBUG_LOG(("Call Info: State changed to Established."));
+		setStateQueued(State::Established);
 	} break;
 
 	case STATE_FAILED: {
 		DEBUG_LOG(("Call Info: State changed to Failed."));
-		failed();
+		setStateQueued(State::Failed);
 	} break;
 
 	default: LOG(("Call Error: Unexpected state in handleStateChange: %1").arg(state));
 	}
 }
 
-template <typename Type>
-bool Call::checkCallCommonFields(const Type &call) {
+template <typename T>
+bool Call::checkCallCommonFields(const T &call) {
 	auto checkFailed = [this] {
-		failed();
+		setState(State::Failed);
 		return false;
 	};
 	if (call.vaccess_hash.v != _accessHash) {
-		LOG(("API Error: Wrong call access_hash."));
+		LOG(("Call Error: Wrong call access_hash."));
 		return checkFailed();
 	}
-	if (call.vadmin_id.v != AuthSession::CurrentUserId()) {
-		LOG(("API Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id.v).arg(AuthSession::CurrentUserId()));
+	auto adminId = (_type == Type::Outgoing) ? AuthSession::CurrentUserId() : peerToUser(_user->id);
+	auto participantId = (_type == Type::Outgoing) ? peerToUser(_user->id) : AuthSession::CurrentUserId();
+	if (call.vadmin_id.v != adminId) {
+		LOG(("Call Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id.v).arg(adminId));
 		return checkFailed();
 	}
-	if (call.vparticipant_id.v != peerToUser(_user->id)) {
-		LOG(("API Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id.v).arg(peerToUser(_user->id)));
+	if (call.vparticipant_id.v != participantId) {
+		LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id.v).arg(participantId));
 		return checkFailed();
 	}
 	return true;
@@ -314,8 +462,8 @@ bool Call::checkCallFields(const MTPDphoneCall &call) {
 		return false;
 	}
 	if (call.vkey_fingerprint.v != _keyFingerprint) {
-		LOG(("API Error: Wrong call fingerprint."));
-		failed();
+		LOG(("Call Error: Wrong call fingerprint."));
+		setState(State::Failed);
 		return false;
 	}
 	return true;
@@ -325,7 +473,64 @@ bool Call::checkCallFields(const MTPDphoneCallAccepted &call) {
 	return checkCallCommonFields(call);
 }
 
-void Call::destroyController() {
+void Call::setState(State state) {
+	if (_state != state) {
+		_state = state;
+		_stateChanged.notify(state, true);
+
+		switch (_state) {
+		case State::WaitingInit:
+		case State::WaitingInitAck:
+		case State::Established:
+			_startTime = getms(true);
+			break;
+		case State::Ended:
+			_delegate->callFinished(this);
+			break;
+		case State::Failed:
+			_delegate->callFailed(this);
+			break;
+		case State::Busy:
+			_hangupByTimeoutTimer.call(kHangupTimeoutMs, [this] { setState(State::Ended); });
+			// TODO play sound
+			break;
+		}
+	}
+}
+
+void Call::finish(const MTPPhoneCallDiscardReason &reason) {
+	if (_state == State::Requesting) {
+		_hangupByTimeoutTimer.call(kHangupTimeoutMs, [this] { setState(State::Ended); });
+		_finishAfterRequestingCall = true;
+		return;
+	}
+	if (_state == State::HangingUp || _state == State::Ended) {
+		return;
+	}
+	if (!_id) {
+		setState(State::Ended);
+		return;
+	}
+
+	setState(State::HangingUp);
+	auto duration = _startTime ? static_cast<int>((getms(true) - _startTime) / 1000) : 0;
+	auto connectionId = _controller ? _controller->GetPreferredRelayID() : 0;
+	_hangupByTimeoutTimer.call(kHangupTimeoutMs, [this] { setState(State::Ended); });
+	request(MTPphone_DiscardCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_int(duration), reason, MTP_long(connectionId))).done([this](const MTPUpdates &result) {
+		// This could be destroyed by updates, so we set Ended after
+		// updates being handled, but in a guarded way.
+		InvokeQueued(this, [this] { setState(State::Ended); });
+		App::main()->sentUpdatesReceived(result);
+	}).fail([this](const RPCError &error) {
+		setState(State::Ended);
+	}).send();
+}
+
+void Call::setStateQueued(State state) {
+	InvokeQueued(this, [this, state] { setState(state); });
+}
+
+Call::~Call() {
 	if (_controller) {
 		DEBUG_LOG(("Call Info: Destroying call controller.."));
 		_controller.reset();
@@ -333,12 +538,4 @@ void Call::destroyController() {
 	}
 }
 
-void Call::failed() {
-	InvokeQueued(this, [this] { _delegate->callFailed(this); });
-}
-
-Call::~Call() {
-	destroyController();
-}
-
 } // namespace Calls
diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h
index 350e255d2..bb6e274b7 100644
--- a/Telegram/SourceFiles/calls/calls_call.h
+++ b/Telegram/SourceFiles/calls/calls_call.h
@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 #include "base/weak_unique_ptr.h"
 #include "mtproto/sender.h"
+#include "base/timer.h"
 
 namespace tgvoip {
 class VoIPController;
@@ -40,43 +41,97 @@ public:
 	class Delegate {
 	public:
 		virtual DhConfig getDhConfig() const = 0;
-		virtual void callFinished(gsl::not_null<Call*> call, const MTPPhoneCallDiscardReason &reason) = 0;
+		virtual void callFinished(gsl::not_null<Call*> call) = 0;
 		virtual void callFailed(gsl::not_null<Call*> call) = 0;
 
 	};
 
-	static constexpr auto kSaltSize = 256;
+	static constexpr auto kRandomPowerSize = 256;
 
-	Call(gsl::not_null<Delegate*> instance, gsl::not_null<UserData*> user);
+	enum class Type {
+		Incoming,
+		Outgoing,
+	};
+	Call(gsl::not_null<Delegate*> delegate, gsl::not_null<UserData*> user, Type type);
 
-	void startOutgoing(base::const_byte_span random);
+	Type type() const {
+		return _type;
+	}
+	gsl::not_null<UserData*> user() const {
+		return _user;
+	}
+
+	void start(base::const_byte_span random);
 	bool handleUpdate(const MTPPhoneCall &call);
 
+	enum State {
+		WaitingInit,
+		WaitingInitAck,
+		Established,
+		Failed,
+		HangingUp,
+		Ended,
+		ExchangingKeys,
+		Waiting,
+		Requesting,
+		WaitingIncoming,
+		Ringing,
+		Busy,
+	};
+	State state() const {
+		return _state;
+	}
+	base::Observable<State> &stateChanged() {
+		return _stateChanged;
+	}
+	void setMute(bool mute);
+
+	void answer();
+	void hangup();
+	void decline();
+
 	~Call();
 
 private:
 	static constexpr auto kAuthKeySize = 256;
+	static constexpr auto kSha256Size = 32;
 
-	void generateSalt(base::const_byte_span random);
+	void finish(const MTPPhoneCallDiscardReason &reason);
+	void startOutgoing();
+	void startIncoming();
+
+	void generateRandomPower(base::const_byte_span random);
 	void handleControllerStateChange(tgvoip::VoIPController *controller, int state);
 	void createAndStartController(const MTPDphoneCall &call);
-	void destroyController();
 
-	template <typename Type>
-	bool checkCallCommonFields(const Type &call);
+	template <typename T>
+	bool checkCallCommonFields(const T &call);
 	bool checkCallFields(const MTPDphoneCall &call);
 	bool checkCallFields(const MTPDphoneCallAccepted &call);
 
 	void confirmAcceptedCall(const MTPDphoneCallAccepted &call);
+	void startConfirmedCall(const MTPDphoneCall &call);
+	void setState(State state);
+	void setStateQueued(State state);
 
-	void failed();
-
-	DhConfig _dhConfig;
 	gsl::not_null<Delegate*> _delegate;
 	gsl::not_null<UserData*> _user;
-	std::vector<gsl::byte> _g_a;
-	std::array<gsl::byte, kSaltSize> _salt;
+	Type _type = Type::Outgoing;
+	State _state = State::WaitingInit;
+	bool _finishAfterRequestingCall = false;
+	base::Observable<State> _stateChanged;
+	TimeMs _startTime = 0;
+	base::DelayedCallTimer _hangupByTimeoutTimer;
+	bool _mute = false;
+
+	DhConfig _dhConfig;
+	std::vector<gsl::byte> _ga;
+	std::vector<gsl::byte> _gb;
+	std::array<gsl::byte, kSha256Size> _gaHash;
+	std::array<gsl::byte, kRandomPowerSize> _randomPower;
 	std::array<gsl::byte, kAuthKeySize> _authKey;
+	MTPPhoneCallProtocol _protocol;
+
 	uint64 _id = 0;
 	uint64 _accessHash = 0;
 	uint64 _keyFingerprint = 0;
diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp
index 6a98c96f3..ce222b154 100644
--- a/Telegram/SourceFiles/calls/calls_instance.cpp
+++ b/Telegram/SourceFiles/calls/calls_instance.cpp
@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "mtproto/connection.h"
 #include "auth_session.h"
 #include "calls/calls_call.h"
+#include "calls/calls_panel.h"
 
 namespace Calls {
 
@@ -32,14 +33,32 @@ void Instance::startOutgoingCall(gsl::not_null<UserData*> user) {
 	if (_currentCall) {
 		return; // Already in a call.
 	}
+	createCall(user, Call::Type::Outgoing);
+}
 
-	_currentCall = std::make_unique<Call>(getCallDelegate(), user);
-	request(MTPmessages_GetDhConfig(MTP_int(_dhConfig.version), MTP_int(Call::kSaltSize))).done([this, call = base::weak_unique_ptr<Call>(_currentCall)](const MTPmessages_DhConfig &result) {
-		if (!call) {
-			DEBUG_LOG(("API Warning: call was destroyed before got dhConfig."));
-			return;
-		}
+void Instance::callFinished(gsl::not_null<Call*> call) {
+	if (_currentCall.get() == call) {
+		_currentCallPanel.reset();
+		_currentCall.reset();
+	}
+}
 
+void Instance::callFailed(gsl::not_null<Call*> call) {
+	if (_currentCall.get() == call) {
+		_currentCallPanel.reset();
+		_currentCall.reset();
+	}
+}
+
+void Instance::createCall(gsl::not_null<UserData*> user, Call::Type type) {
+	_currentCall = std::make_unique<Call>(getCallDelegate(), user, type);
+	_currentCallPanel = std::make_unique<Panel>(_currentCall.get());
+	refreshDhConfig();
+}
+
+void Instance::refreshDhConfig() {
+	Expects(_currentCall != nullptr);
+	request(MTPmessages_GetDhConfig(MTP_int(_dhConfig.version), MTP_int(Call::kRandomPowerSize))).done([this, call = base::weak_unique_ptr<Call>(_currentCall)](const MTPmessages_DhConfig &result) {
 		auto random = base::const_byte_span();
 		switch (result.type()) {
 		case mtpc_messages_dhConfig: {
@@ -67,32 +86,22 @@ void Instance::startOutgoingCall(gsl::not_null<UserData*> user) {
 		default: Unexpected("Type in messages.getDhConfig");
 		}
 
-		if (random.size() != Call::kSaltSize) {
+		if (random.size() != Call::kRandomPowerSize) {
 			LOG(("API Error: dhConfig random bytes wrong size: %1").arg(random.size()));
 			callFailed(call.get());
 			return;
 		}
-		call->startOutgoing(random);
+		if (call) {
+			call->start(random);
+		}
 	}).fail([this, call = base::weak_unique_ptr<Call>(_currentCall)](const RPCError &error) {
 		if (!call) {
 			DEBUG_LOG(("API Warning: call was destroyed before got dhConfig."));
 			return;
 		}
-
 		callFailed(call.get());
 	}).send();
-}
 
-void Instance::callFinished(gsl::not_null<Call*> call, const MTPPhoneCallDiscardReason &reason) {
-	if (_currentCall.get() == call) {
-		_currentCall.reset();
-	}
-}
-
-void Instance::callFailed(gsl::not_null<Call*> call) {
-	if (_currentCall.get() == call) {
-		_currentCall.reset();
-	}
 }
 
 void Instance::handleUpdate(const MTPDupdatePhoneCall& update) {
@@ -101,10 +110,18 @@ void Instance::handleUpdate(const MTPDupdatePhoneCall& update) {
 
 void Instance::handleCallUpdate(const MTPPhoneCall &call) {
 	if (call.type() == mtpc_phoneCallRequested) {
-		if (_currentCall) {
-			// discard ?
+		auto &phoneCall = call.c_phoneCallRequested();
+		auto user = App::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."));
+		}
+		if (_currentCall || !user || user->isSelf()) {
+			request(MTPphone_DiscardCall(MTP_inputPhoneCall(phoneCall.vid, phoneCall.vaccess_hash), MTP_int(0), MTP_phoneCallDiscardReasonBusy(), MTP_long(0))).send();
 		} else {
-			// show call
+			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()));
diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h
index ed3846ffe..406fbecef 100644
--- a/Telegram/SourceFiles/calls/calls_instance.h
+++ b/Telegram/SourceFiles/calls/calls_instance.h
@@ -25,7 +25,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 
 namespace Calls {
 
-class Instance : private MTP::Sender, private Call::Delegate {
+class Panel;
+
+class Instance : private MTP::Sender, private Call::Delegate, private base::Subscriber {
 public:
 	Instance();
 
@@ -42,14 +44,17 @@ private:
 	DhConfig getDhConfig() const override {
 		return _dhConfig;
 	}
-	void callFinished(gsl::not_null<Call*> call, const MTPPhoneCallDiscardReason &reason) override;
+	void callFinished(gsl::not_null<Call*> call) override;
 	void callFailed(gsl::not_null<Call*> call) override;
+	void createCall(gsl::not_null<UserData*> user, Call::Type type);
+	void refreshDhConfig();
 
 	void handleCallUpdate(const MTPPhoneCall &call);
 
 	DhConfig _dhConfig;
 
 	std::unique_ptr<Call> _currentCall;
+	std::unique_ptr<Panel> _currentCallPanel;
 
 };
 
diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp
new file mode 100644
index 000000000..3d1cf7fa7
--- /dev/null
+++ b/Telegram/SourceFiles/calls/calls_panel.cpp
@@ -0,0 +1,320 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#include "calls/calls_panel.h"
+
+#include "calls/calls_call.h"
+#include "styles/style_calls.h"
+#include "styles/style_history.h"
+#include "ui/widgets/buttons.h"
+#include "ui/widgets/labels.h"
+#include "ui/effects/ripple_animation.h"
+#include "ui/widgets/shadow.h"
+#include "messenger.h"
+#include "auth_session.h"
+#include "apiwrap.h"
+#include "platform/platform_specific.h"
+
+namespace Calls {
+
+class Panel::Button : public Ui::RippleButton {
+public:
+	Button(QWidget *parent, const style::CallButton &st);
+
+protected:
+	void paintEvent(QPaintEvent *e) override;
+
+	void onStateChanged(State was, StateChangeSource source) override;
+
+	QImage prepareRippleMask() const override;
+	QPoint prepareRippleStartPosition() const override;
+
+private:
+	const style::CallButton &_st;
+	QPixmap _bg;
+
+};
+
+Panel::Button::Button(QWidget *parent, const style::CallButton &st) : Ui::RippleButton(parent, st.button.ripple)
+, _st(st) {
+	resize(_st.button.width, _st.button.height);
+	_bg = App::pixmapFromImageInPlace(style::colorizeImage(prepareRippleMask(), _st.bg));
+}
+
+void Panel::Button::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+
+	p.drawPixmap(myrtlpoint(_st.button.rippleAreaPosition), _bg);
+
+	auto ms = getms();
+
+	paintRipple(p, _st.button.rippleAreaPosition.x(), _st.button.rippleAreaPosition.y(), ms);
+
+	auto down = isDown();
+	auto position = _st.button.iconPosition;
+	_st.button.icon.paint(p, position, width());
+}
+
+void Panel::Button::onStateChanged(State was, StateChangeSource source) {
+	RippleButton::onStateChanged(was, source);
+
+	auto over = isOver();
+	auto wasOver = static_cast<bool>(was & StateFlag::Over);
+	if (over != wasOver) {
+		update();
+	}
+}
+
+QPoint Panel::Button::prepareRippleStartPosition() const {
+	return mapFromGlobal(QCursor::pos()) - _st.button.rippleAreaPosition;
+}
+
+QImage Panel::Button::prepareRippleMask() const {
+	return Ui::RippleAnimation::ellipseMask(QSize(_st.button.rippleAreaSize, _st.button.rippleAreaSize));
+}
+
+Panel::Panel(gsl::not_null<Call*> call)
+: _call(call)
+, _user(call->user())
+, _hangup(this, st::callHangup)
+, _mute(this, st::callMuteToggle)
+, _name(this)
+, _status(this) {
+	initControls();
+	initLayout();
+	show();
+}
+
+void Panel::initControls() {
+	subscribe(_call->stateChanged(), [this](Call::State state) {
+		if (state == Call::State::Failed || state == Call::State::Ended) {
+			callDestroyed();
+		}
+	});
+	_hangup->setClickedCallback([this] {
+		if (_call) {
+			_call->hangup();
+		}
+	});
+	if (_call->type() == Call::Type::Incoming) {
+		_answer.create(this, st::callAnswer);
+		_answer->setClickedCallback([this] {
+			if (_call) {
+				_call->answer();
+			}
+		});
+	}
+}
+
+void Panel::initLayout() {
+	hide();
+
+	setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | /*Qt::WindowStaysOnTopHint | */Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint | Qt::Tool);
+	setAttribute(Qt::WA_MacAlwaysShowToolWindow);
+	setAttribute(Qt::WA_NoSystemBackground, true);
+	setAttribute(Qt::WA_TranslucentBackground, true);
+
+	initGeometry();
+
+	processUserPhoto();
+	subscribe(AuthSession::Current().api().fullPeerUpdated(), [this](PeerData *peer) {
+		if (peer == _user) {
+			processUserPhoto();
+		}
+	});
+	subscribe(AuthSession::CurrentDownloaderTaskFinished(), [this] {
+		refreshUserPhoto();
+	});
+	createDefaultCacheImage();
+}
+
+void Panel::processUserPhoto() {
+	if (!_user->userpicLoaded()) {
+		_user->loadUserpic(true);
+	}
+	auto photo = (_user->photoId && _user->photoId != UnknownPeerPhotoId) ? App::photo(_user->photoId) : nullptr;
+	if (isGoodUserPhoto(photo)) {
+		photo->full->load(true);
+	} else {
+		if ((_user->photoId == UnknownPeerPhotoId) || (_user->photoId && (!photo || !photo->date))) {
+			App::api()->requestFullPeer(_user);
+		}
+	}
+	refreshUserPhoto();
+}
+
+void Panel::refreshUserPhoto() {
+	auto photo = (_user->photoId && _user->photoId != UnknownPeerPhotoId) ? App::photo(_user->photoId) : nullptr;
+	if (isGoodUserPhoto(photo) && photo->full->loaded() && (photo->id != _userPhotoId || !_userPhotoFull)) {
+		_userPhotoId = photo->id;
+		_userPhotoFull = true;
+		createUserpicCache(photo->full);
+	} else if (_userPhoto.isNull()) {
+		if (auto userpic = _user->currentUserpic()) {
+			createUserpicCache(userpic);
+		}
+	}
+}
+
+void Panel::createUserpicCache(ImagePtr image) {
+	auto size = st::callWidth * cIntRetinaFactor();
+	auto options = _useTransparency ? (Images::Option::RoundedLarge | Images::Option::RoundedTopLeft | Images::Option::RoundedTopRight | Images::Option::Smooth) : 0;
+	auto width = image->width();
+	auto height = image->height();
+	if (width > height) {
+		width = qMax((width * size) / height, 1);
+		height = size;
+	} else {
+		height = qMax((height * size) / width, 1);
+		width = size;
+	}
+	_userPhoto = image->pixNoCache(width, height, options, size, size);
+	if (cRetina()) _userPhoto.setDevicePixelRatio(cRetinaFactor());
+
+	refreshCacheImageUserPhoto();
+
+	update();
+}
+
+bool Panel::isGoodUserPhoto(PhotoData *photo) {
+	if (!photo || !photo->date) {
+		return false;
+	}
+	auto badAspect = [](int a, int b) {
+		return a > 10 * b;
+	};
+	auto width = photo->full->width();
+	auto height = photo->full->height();
+	return !badAspect(width, height) && !badAspect(height, width);
+}
+
+void Panel::initGeometry() {
+	auto center = Messenger::Instance().getPointForCallPanelCenter();
+	_useTransparency = Platform::TransparentWindowsSupported(center);
+	_padding = _useTransparency ? st::callShadow.extend : style::margins();
+	_contentTop = _padding.top() + st::callWidth;
+	auto screen = QApplication::desktop()->screenGeometry(center);
+	auto rect = QRect(0, 0, st::callWidth, st::callHeight);
+	setGeometry(rect.translated(center - rect.center()).marginsAdded(_padding));
+	createBottomImage();
+}
+
+void Panel::createBottomImage() {
+	if (!_useTransparency) {
+		return;
+	}
+	auto bottomWidth = width();
+	auto bottomHeight = height() - _padding.top() - st::callWidth;
+	auto image = QImage(QSize(bottomWidth, bottomHeight) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
+	image.fill(Qt::transparent);
+	{
+		Painter p(&image);
+		Ui::Shadow::paint(p, QRect(_padding.left(), 0, st::callWidth, bottomHeight - _padding.bottom()), width(), st::callShadow, Ui::Shadow::Side::Left | Ui::Shadow::Side::Right | Ui::Shadow::Side::Bottom);
+		p.setCompositionMode(QPainter::CompositionMode_Source);
+		p.setBrush(st::callBg);
+		p.setPen(Qt::NoPen);
+		PainterHighQualityEnabler hq(p);
+		p.drawRoundedRect(myrtlrect(_padding.left(), -st::historyMessageRadius, st::callWidth, bottomHeight - _padding.bottom() + st::historyMessageRadius), st::historyMessageRadius, st::historyMessageRadius);
+	}
+	_bottomCache = App::pixmapFromImageInPlace(std::move(image));
+}
+
+void Panel::createDefaultCacheImage() {
+	if (!_useTransparency || !_cache.isNull()) {
+		return;
+	}
+	auto cache = QImage(size(), QImage::Format_ARGB32_Premultiplied);
+	cache.fill(Qt::transparent);
+	{
+		Painter p(&cache);
+		auto inner = rect().marginsRemoved(_padding);
+		Ui::Shadow::paint(p, inner, width(), st::callShadow);
+		p.setCompositionMode(QPainter::CompositionMode_Source);
+		p.setBrush(st::callBg);
+		p.setPen(Qt::NoPen);
+		PainterHighQualityEnabler hq(p);
+		p.drawRoundedRect(myrtlrect(inner), st::historyMessageRadius, st::historyMessageRadius);
+	}
+	_cache = App::pixmapFromImageInPlace(std::move(cache));
+}
+
+void Panel::refreshCacheImageUserPhoto() {
+	auto cache = QImage(size(), QImage::Format_ARGB32_Premultiplied);
+	cache.fill(Qt::transparent);
+	{
+		Painter p(&cache);
+		Ui::Shadow::paint(p, QRect(_padding.left(), _padding.top(), st::callWidth, st::callWidth), width(), st::callShadow, Ui::Shadow::Side::Top | Ui::Shadow::Side::Left | Ui::Shadow::Side::Right);
+		p.drawPixmapLeft(_padding.left(), _padding.top(), width(), _userPhoto);
+		p.drawPixmapLeft(0, _padding.top() + st::callWidth, width(), _bottomCache);
+	}
+	_cache = App::pixmapFromImageInPlace(std::move(cache));
+}
+
+void Panel::resizeEvent(QResizeEvent *e) {
+	auto controlsTop = _contentTop + st::callControlsTop;
+	if (_answer) {
+		auto bothWidth = _answer->width() + st::callControlsSkip + _hangup->width();
+		_hangup->moveToLeft((width() - bothWidth) / 2, controlsTop);
+		_answer->moveToRight((width() - bothWidth) / 2, controlsTop);
+	} else {
+		_hangup->moveToLeft((width() - _hangup->width()) / 2, controlsTop);
+	}
+	_mute->moveToRight(_padding.right() + st::callMuteRight, controlsTop);
+}
+
+void Panel::paintEvent(QPaintEvent *e) {
+	Painter p(this);
+	if (_useTransparency) {
+		p.drawPixmapLeft(0, 0, width(), _cache);
+	} else {
+		p.drawPixmapLeft(0, 0, width(), _userPhoto);
+		p.fillRect(myrtlrect(0, st::callWidth, width(), height() - st::callWidth), st::callBg);
+	}
+}
+
+void Panel::mousePressEvent(QMouseEvent *e) {
+	auto dragArea = myrtlrect(_padding.left(), _padding.top(), st::callWidth, st::callWidth);
+	if (e->button() == Qt::LeftButton && dragArea.contains(e->pos())) {
+		_dragging = true;
+		_dragStartMousePosition = e->globalPos();
+		_dragStartMyPosition = QPoint(x(), y());
+	}
+}
+
+void Panel::mouseMoveEvent(QMouseEvent *e) {
+	if (_dragging) {
+		if (!(e->buttons() & Qt::LeftButton)) {
+			_dragging = false;
+		} else {
+			move(_dragStartMyPosition + (e->globalPos() - _dragStartMousePosition));
+		}
+	}
+}
+
+void Panel::mouseReleaseEvent(QMouseEvent *e) {
+	if (e->button() == Qt::LeftButton) {
+		_dragging = false;
+	}
+}
+
+void Panel::callDestroyed() {
+}
+
+} // namespace Calls
diff --git a/Telegram/SourceFiles/calls/calls_panel.h b/Telegram/SourceFiles/calls/calls_panel.h
new file mode 100644
index 000000000..bd1235ed0
--- /dev/null
+++ b/Telegram/SourceFiles/calls/calls_panel.h
@@ -0,0 +1,89 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop version of Telegram messaging app, see https://telegram.org
+
+Telegram Desktop is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+It is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+In addition, as a special exception, the copyright holders give permission
+to link the code of portions of this program with the OpenSSL library.
+
+Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
+Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
+*/
+#pragma once
+
+#include "base/weak_unique_ptr.h"
+
+namespace Ui {
+class IconButton;
+class FlatLabel;
+} // namespace Ui
+
+namespace Calls {
+
+class Call;
+
+class Panel : public TWidget, private base::Subscriber {
+public:
+	Panel(gsl::not_null<Call*> call);
+
+protected:
+	void paintEvent(QPaintEvent *e) override;
+	void resizeEvent(QResizeEvent *e) override;
+	void mousePressEvent(QMouseEvent *e) override;
+	void mouseReleaseEvent(QMouseEvent *e) override;
+	void mouseMoveEvent(QMouseEvent *e) override;
+
+private:
+	void initControls();
+	void initLayout();
+	void initGeometry();
+	void createBottomImage();
+	void createDefaultCacheImage();
+	void refreshCacheImageUserPhoto();
+
+	void processUserPhoto();
+	void refreshUserPhoto();
+	bool isGoodUserPhoto(PhotoData *photo);
+	void createUserpicCache(ImagePtr image);
+
+	void callDestroyed();
+
+	base::weak_unique_ptr<Call> _call;
+	gsl::not_null<UserData*> _user;
+
+	bool _useTransparency = true;
+	style::margins _padding;
+	int _contentTop = 0;
+
+	bool _dragging = false;
+	QPoint _dragStartMousePosition;
+	QPoint _dragStartMyPosition;
+
+	class Button;
+	object_ptr<Ui::IconButton> _close = { nullptr };
+	object_ptr<Button> _answer = { nullptr };
+	object_ptr<Button> _hangup;
+	object_ptr<Ui::IconButton> _mute;
+	object_ptr<Ui::FlatLabel> _name;
+	object_ptr<Ui::FlatLabel> _status;
+	std::vector<EmojiPtr> _fingerprint;
+
+	QPixmap _userPhoto;
+	PhotoId _userPhotoId = 0;
+	bool _userPhotoFull = false;
+
+	QPixmap _bottomCache;
+	QPixmap _cache;
+
+};
+
+} // namespace Calls
diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h
index 8dd907f28..c320b8815 100644
--- a/Telegram/SourceFiles/core/utils.h
+++ b/Telegram/SourceFiles/core/utils.h
@@ -210,6 +210,10 @@ inline void copy_bytes(byte_span destination, const_byte_span source) {
 	memcpy(destination.data(), source.data(), source.size());
 }
 
+inline void set_bytes(byte_span destination, gsl::byte value) {
+	memset(destination.data(), gsl::to_integer<unsigned char>(value), destination.size());
+}
+
 } // namespace base
 
 // using for_const instead of plain range-based for loop to ensure usage of const_iterator
diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp
index 3b4cc7fa0..252783009 100644
--- a/Telegram/SourceFiles/messenger.cpp
+++ b/Telegram/SourceFiles/messenger.cpp
@@ -777,3 +777,12 @@ Messenger::~Messenger() {
 MainWindow *Messenger::mainWindow() {
 	return _window.get();
 }
+
+QPoint Messenger::getPointForCallPanelCenter() const {
+	Expects(_window != nullptr);
+	Expects(_window->windowHandle() != nullptr);
+	if (_window->isActive()) {
+		return _window->geometry().center();
+	}
+	return _window->windowHandle()->screen()->geometry().center();
+}
diff --git a/Telegram/SourceFiles/messenger.h b/Telegram/SourceFiles/messenger.h
index 152935815..a4ea8032e 100644
--- a/Telegram/SourceFiles/messenger.h
+++ b/Telegram/SourceFiles/messenger.h
@@ -50,6 +50,7 @@ public:
 	~Messenger();
 
 	MainWindow *mainWindow();
+	QPoint getPointForCallPanelCenter() const;
 
 	static Messenger *InstancePointer();
 	static Messenger &Instance() {
diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h
index 2c4eb2803..e660fb7f2 100644
--- a/Telegram/SourceFiles/structs.h
+++ b/Telegram/SourceFiles/structs.h
@@ -377,11 +377,12 @@ public:
 		return _openLink;
 	}
 
+	ImagePtr currentUserpic() const;
+
 protected:
 	void updateNameDelayed(const QString &newName, const QString &newNameOrPhone, const QString &newUsername);
 
 	ImagePtr _userpic;
-	ImagePtr currentUserpic() const;
 	mutable EmptyUserpic _userpicEmpty;
 
 private:
diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style
index 6472d7390..51626c789 100644
--- a/Telegram/SourceFiles/ui/widgets/widgets.style
+++ b/Telegram/SourceFiles/ui/widgets/widgets.style
@@ -352,6 +352,11 @@ MultiSelect {
 	fieldCancelSkip: pixels;
 }
 
+CallButton {
+	button: IconButton;
+	bg: color;
+}
+
 Menu {
 	skip: pixels;
 
diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp
index cee607fe8..49b2e030a 100644
--- a/Telegram/gyp/Telegram.gyp
+++ b/Telegram/gyp/Telegram.gyp
@@ -37,6 +37,7 @@
         '<(res_loc)/colors.palette',
         '<(res_loc)/basic.style',
         '<(src_loc)/boxes/boxes.style',
+        '<(src_loc)/calls/calls.style',
         '<(src_loc)/dialogs/dialogs.style',
         '<(src_loc)/history/history.style',
         '<(src_loc)/intro/intro.style',
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index 5dffcd437..dc6e4082e 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -3,6 +3,7 @@
 <(src_loc)/base/observer.cpp
 <(src_loc)/base/observer.h
 <(src_loc)/base/ordered_set.h
+<(src_loc)/base/openssl_help.h
 <(src_loc)/base/parse_helper.cpp
 <(src_loc)/base/parse_helper.h
 <(src_loc)/base/qthelp_regex.h
@@ -83,6 +84,8 @@
 <(src_loc)/calls/calls_call.h
 <(src_loc)/calls/calls_instance.cpp
 <(src_loc)/calls/calls_instance.h
+<(src_loc)/calls/calls_panel.cpp
+<(src_loc)/calls/calls_panel.h
 <(src_loc)/chat_helpers/bot_keyboard.cpp
 <(src_loc)/chat_helpers/bot_keyboard.h
 <(src_loc)/chat_helpers/emoji_list_widget.cpp