From ea2ed85ed7608e7a9f2b8027259dad6dac6cf581 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 16 Apr 2020 17:38:05 +0200 Subject: [PATCH] support picture in picture for video calls --- src/main/AndroidManifest.xml | 4 +- .../conversations/ui/RtpSessionActivity.java | 108 ++++++++++++------ .../drawable-hdpi/ic_warning_white_48dp.png | Bin 0 -> 714 bytes .../drawable-mdpi/ic_warning_white_48dp.png | Bin 0 -> 364 bytes .../drawable-xhdpi/ic_warning_white_48dp.png | Bin 0 -> 590 bytes .../drawable-xxhdpi/ic_warning_white_48dp.png | Bin 0 -> 843 bytes .../ic_warning_white_48dp.png | Bin 0 -> 1044 bytes src/main/res/layout/activity_rtp_session.xml | 20 +++- 8 files changed, 95 insertions(+), 37 deletions(-) create mode 100644 src/main/res/drawable-hdpi/ic_warning_white_48dp.png create mode 100644 src/main/res/drawable-mdpi/ic_warning_white_48dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_warning_white_48dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_warning_white_48dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_warning_white_48dp.png diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 8b0cbd6f1..29356fb21 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -292,7 +292,9 @@ android:name=".ui.ChannelDiscoveryActivity" android:label="@string/discover_channels" /> + android:supportsPictureInPicture="true" + android:launchMode="singleTask" + android:autoRemoveFromRecents="true"/> diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 8883ec9fa..cff788e04 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.ui; import android.Manifest; import android.annotation.SuppressLint; +import android.app.PictureInPictureParams; import android.content.Context; import android.content.Intent; import android.databinding.DataBindingUtil; @@ -11,6 +12,7 @@ import android.os.PowerManager; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.util.Log; +import android.util.Rational; import android.view.View; import android.view.WindowManager; import android.widget.Toast; @@ -19,6 +21,7 @@ import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import org.webrtc.PeerConnection; import org.webrtc.RendererCommon; import org.webrtc.SurfaceViewRenderer; import org.webrtc.VideoTrack; @@ -47,31 +50,33 @@ import static java.util.Arrays.asList; public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate { + public static final String EXTRA_WITH = "with"; + public static final String EXTRA_SESSION_ID = "session_id"; + public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state"; + public static final String EXTRA_LAST_ACTION = "last_action"; + public static final String ACTION_ACCEPT_CALL = "action_accept_call"; + public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call"; + public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call"; private static final List END_CARD = Arrays.asList( RtpEndUserState.APPLICATION_ERROR, RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.CONNECTIVITY_ERROR ); - private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session"; - private static final int REQUEST_ACCEPT_CALL = 0x1111; - - public static final String EXTRA_WITH = "with"; - public static final String EXTRA_SESSION_ID = "session_id"; - public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state"; - public static final String EXTRA_LAST_ACTION = "last_action"; - - public static final String ACTION_ACCEPT_CALL = "action_accept_call"; - public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call"; - public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call"; - - private WeakReference rtpConnectionReference; private ActivityRtpSessionBinding binding; private PowerManager.WakeLock mProximityWakeLock; + private static Set actionToMedia(final String action) { + if (ACTION_MAKE_VIDEO_CALL.equals(action)) { + return ImmutableSet.of(Media.AUDIO, Media.VIDEO); + } else { + return ImmutableSet.of(Media.AUDIO); + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -235,14 +240,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } } - private static Set actionToMedia(final String action) { - if (ACTION_MAKE_VIDEO_CALL.equals(action)) { - return ImmutableSet.of(Media.AUDIO, Media.VIDEO); - } else { - return ImmutableSet.of(Media.AUDIO); - } - } - private void proposeJingleRtpSession(final Account account, final Jid with, final Set media) { xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with, media); putScreenInCallMode(media); @@ -284,6 +281,29 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe super.onBackPressed(); } + @Override + public void onUserLeaveHint() { + Log.d(Config.LOGTAG, "user leave hint"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (shouldBePictureInPicture()) { + enterPictureInPictureMode( + new PictureInPictureParams.Builder() + .setAspectRatio(new Rational(10, 16)) + .build() + ); + } + } + + } + + private boolean shouldBePictureInPicture() { + try { + final JingleRtpConnection rtpConnection = requireRtpConnection(); + return rtpConnection.getMedia().contains(Media.VIDEO) && rtpConnection.getEndUserState() == RtpEndUserState.CONNECTED; + } catch (IllegalStateException e) { + return false; + } + } private void initializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) { final WeakReference reference = xmppConnectionService.getJingleConnectionManager() @@ -380,7 +400,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @SuppressLint("RestrictedApi") private void updateButtonConfiguration(final RtpEndUserState state) { - if (state == RtpEndUserState.INCOMING_CALL) { + if (state == RtpEndUserState.ENDING_CALL || isPictureInPicture()) { + this.binding.rejectCall.setVisibility(View.INVISIBLE); + this.binding.endCall.setVisibility(View.INVISIBLE); + this.binding.acceptCall.setVisibility(View.INVISIBLE); + } else if (state == RtpEndUserState.INCOMING_CALL) { this.binding.rejectCall.setOnClickListener(this::rejectCall); this.binding.rejectCall.setImageResource(R.drawable.ic_call_end_white_48dp); this.binding.rejectCall.setVisibility(View.VISIBLE); @@ -388,10 +412,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe this.binding.acceptCall.setOnClickListener(this::acceptCall); this.binding.acceptCall.setImageResource(R.drawable.ic_call_white_48dp); this.binding.acceptCall.setVisibility(View.VISIBLE); - } else if (state == RtpEndUserState.ENDING_CALL) { - this.binding.rejectCall.setVisibility(View.INVISIBLE); - this.binding.endCall.setVisibility(View.INVISIBLE); - this.binding.acceptCall.setVisibility(View.INVISIBLE); } else if (state == RtpEndUserState.DECLINED_OR_BUSY) { this.binding.rejectCall.setVisibility(View.INVISIBLE); this.binding.endCall.setOnClickListener(this::exit); @@ -416,13 +436,21 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe updateInCallButtonConfiguration(state); } + private boolean isPictureInPicture() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return isInPictureInPictureMode(); + } else { + return false; + } + } + private void updateInCallButtonConfiguration() { updateInCallButtonConfiguration(requireRtpConnection().getEndUserState()); } @SuppressLint("RestrictedApi") private void updateInCallButtonConfiguration(final RtpEndUserState state) { - if (state == RtpEndUserState.CONNECTED) { + if (state == RtpEndUserState.CONNECTED && !isPictureInPicture()) { if (getMedia().contains(Media.VIDEO)) { updateInCallButtonConfigurationVideo(requireRtpConnection().isVideoEnabled()); } else { @@ -513,12 +541,23 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe if (END_CARD.contains(state) || state == RtpEndUserState.ENDING_CALL) { binding.localVideo.setVisibility(View.GONE); binding.remoteVideo.setVisibility(View.GONE); - binding.appBarLayout.setVisibility(View.VISIBLE); + if (isPictureInPicture()) { + binding.appBarLayout.setVisibility(View.GONE); + binding.pipPlaceholder.setVisibility(View.VISIBLE); + if (state == RtpEndUserState.APPLICATION_ERROR || state == RtpEndUserState.CONNECTIVITY_ERROR) { + binding.pipWarning.setVisibility(View.VISIBLE); + } else { + binding.pipWarning.setVisibility(View.GONE); + } + } else { + binding.appBarLayout.setVisibility(View.VISIBLE); + binding.pipPlaceholder.setVisibility(View.GONE); + } getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); return; } final Optional localVideoTrack = requireRtpConnection().geLocalVideoTrack(); - if (localVideoTrack.isPresent()) { + if (localVideoTrack.isPresent() && !isPictureInPicture()) { ensureSurfaceViewRendererIsSetup(binding.localVideo); //paint local view over remote view binding.localVideo.setZOrderMediaOverlay(true); @@ -600,9 +639,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) { Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")"); if (END_CARD.contains(state)) { - Log.d(Config.LOGTAG,"end card reached"); + Log.d(Config.LOGTAG, "end card reached"); releaseProximityWakeLock(); - runOnUiThread(()-> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)); + runOnUiThread(() -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)); } if (with.isBareJid()) { updateRtpSessionProposalState(account, with, state); @@ -610,7 +649,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } if (this.rtpConnectionReference == null) { if (END_CARD.contains(state)) { - Log.d(Config.LOGTAG,"not reinitializing session"); + Log.d(Config.LOGTAG, "not reinitializing session"); return; } //this happens when going from proposed session to actual session @@ -620,6 +659,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe final AbstractJingleConnection.Id id = requireRtpConnection().getId(); if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) { if (state == RtpEndUserState.ENDED) { + resetIntent(account, with, state, requireRtpConnection().getMedia()); finish(); return; } else if (END_CARD.contains(state)) { @@ -641,7 +681,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices); try { if (getMedia().contains(Media.VIDEO)) { - Log.d(Config.LOGTAG,"nothing to do; in video mode"); + Log.d(Config.LOGTAG, "nothing to do; in video mode"); return; } final RtpEndUserState endUserState = requireRtpConnection().getEndUserState(); @@ -652,7 +692,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe audioManager.getAudioDevices().size() ); } else if (END_CARD.contains(endUserState)) { - Log.d(Config.LOGTAG,"onAudioDeviceChanged() nothing to do because end card has been reached"); + Log.d(Config.LOGTAG, "onAudioDeviceChanged() nothing to do because end card has been reached"); } else { putProximityWakeLockInProperState(); } diff --git a/src/main/res/drawable-hdpi/ic_warning_white_48dp.png b/src/main/res/drawable-hdpi/ic_warning_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a8889659086685a6fec08677aa94fded88773bd4 GIT binary patch literal 714 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s zRN>_5;uw-~@9nMqeoT%6$3M!eh=hdP%2bcYSa{LzZR@=c3|{#a3|q}@8g5C;ImkJ@ zHHq?2ypg$FRFsu7LZhI*tUT?+ySt*+S2voM|1?p#FHj_$l3gVC#8Ga^Jgo-5G6&WY z!-HyWk&LE=oC`X`%p6X?Sj>{pVEQM8v84C$%~0jq3uf~s{NFE^%)UOeS#E>)uaXIR zEBbEV@U*rK5VT$6a?WmJUt-jP;yIfB`j;5jK9Y4k`cpu(W+`LVV~^_P^K3HYzaQJ( zf3oRQ#j$A*|8bWY`U#aZX%{_Kvkc&rk5DSx7kErTGk|;fnzd<03%KqDB)sX}&{!w5 zfRFFyY}4-xwBE7YHqE@@<;YqRDE8+2l06~VL!6ZyonI~E@LvHn^98DXq(jpm9W(KYNed3KxVy?*z4+P=(qxw^%g~oADVPU6ngQDd(|5@S0m}nupA|jE=AR+u52clF}C{F3fq` zprb3)T$t;&LPsB*4j1OQZP_8bdYziUwqk+z(?i3Jxo>Npa9yJ(vMqSR?1)%}oWJq9 gHYb{aNa-`TWbR`|xA^#tz$C`t>FVdQ&MBb@07(27d;kCd literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_warning_white_48dp.png b/src/main/res/drawable-mdpi/ic_warning_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a43fa3c27d451e56094e5f182d1820b5424f2987 GIT binary patch literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}cwPFdBQhIEGX(zBy~icQ8Ss{o^vv zcE7ndBxcUsd7zw;?*OX>gZKl6m)_Mj_wyHnbY@Kdom|q>Q!N! zuKrSPN6~>vYs7*aW;d*1iZVJdZ%>)ffeC*S0>n4$oFpH!;WcA$*Q_gyHuprn9=gna zO{M$)$*^4x(>I6+T)N2I7r@of%(q~H3B#+)ak5d297i1l-t1R+#Vl%>6z}j+pLI=# z1bas^mq5_tiscN7s(qFW9-Ya23_*`0*gblaWfe^ON*k_NTw??Z%;)iFP3C6^dU%7m zA;jVv1F{^uN8{r)*^ekp6x00ZyC8Qbxrq8qfR%h9ZNftDnm{r-UW| DcZiV2 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_warning_white_48dp.png b/src/main/res/drawable-xhdpi/ic_warning_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8683a2ea9ad340635e32632d7b1f5234f567d796 GIT binary patch literal 590 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z$EJF;uuoF_~yJJ-@yV2hmXrV z+x_O=keE4h=YjuBG7a1X40;C`cQCL&U^sK8C&y>5l}YBjm7OM6uU&ZZd(!{+b01hv zOO2csD$m2V{G80IGmp>l`oBLQ(=hW|qnaXvBJW)0=pgA{=31d3PTN}M^nG);@b;XP z%9Q$0IqB&6>Y&O4oUnB8F`s4L4{U3W86LLw+dq4vO2uZi62-ZW~as!d{Nx;Ld# z@Puqc@KLW<4%267C0QA<_l1;But@mP{&P$G@ol^;jsh&An}q`8x*8(wYO7d%q*=jDrtIxvIH0W4kk)?u{C7bU_KKDUhFQ!uP7Vxb8RZle8lExq3kWzA zH@I_fEU-MF%))X(=7As+QwHA$4n{_(^1kEhya^dba-SJm)E*o1J7ieN{bp!d_IM+| z0)&~?b6jqgpJHmt$7n@j4dY6|3>|cN|31qJ1o(pdrEX zWp}jP%-b791MajWpEKlqkf|tmZLTr4`HV@-1z-2NcJLm!`dv)Xj3N9v z1Bh+O02D7h(QtzyM%G9HDBR8i5>{l!C49LcwE2cf%vA+5?r__Vvxh?YlavcZn{If- zv?`d1giChx9t~wlN-h*_ywNeghM&2nFCj)%!AvTgv7`6jW7cV1FRt=NcrTt_{~%Gg zcQwNix5rux8!ztQ`VeEXj!8foS;!#=D6~5QNZhUg67LUY#IQn5Iu^>1lx!y3`qf0Aq!EUXW-csDZc@N&*!Sa(T4x?#1+5jKKCjjO*KFyL2r;Hg%v zAU5L~SDznvYn#o+w@M<8dlr_vKluGu{^8?8u}rQn;}|3l zFc>v3rZ6y{U|^fT$fLk4;lO6lz*CS{e|(1cxtS2ukowxX!KpFKV-)gPp1SI@y)H6Hc@9$IRYn?jUFU zO)zqj;epOy$J$?}G<>;cQ+&E@D&r+Co{Q6inN>EVO8&Glc3|R-{ld@1tn%S#OZ74t zo(adA%#+^CkXC4&*Cipkm+@cxY2l4gQOth=^4--14jYKDocwlw^X0~ejrD2ko0w}o z$QQ=iqTgZY|{xg&xhP7;0obEiht` zZ?{>&bKtllzgF{yLjHzRHZ?L#3yk>X+Zk4zIec7^LF@B}LJpk56*Wf|8p+8Qt~hht zU0Un&$BS*Ja%#RTG?JFDU2*1^yS&!sj~Cm?6Iy;AS>yeKZ=+SX4nwX?ewGNr%YEaaL0 z6ogQ~%>CiF%f0`lX2!q!nXmorcIdz9{olkPTJ{;-Y?=<%f^15_OngFxKPvuxd-tHl zQGsERhF|r!`~AEea`v0@w>2KvzoT+S&7Ap;Pah<6PtE=)7{X__t$fbGkkescKO@v@3&qT3fTEAiBIeLBjpt_{rykh zvwjWO3DfiX<*yDdiw<}rw$$$1KJY} zGWoC93YnUmg+(7Ww!(!;6nK6XY8+S}$R8l{zdL1K*HpJC|1 + + + + + + android:visibility="gone" + app:elevation="4dp" />