Browse Source

add grid view for calls. make own video movable

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe 4 years ago
parent
commit
b8e4c4da56

+ 1 - 0
CHANGELOG.md

@@ -10,6 +10,7 @@ Types of changes can be: Added/Changed/Deprecated/Removed/Fixed/Security
 - open files inside app (jpg, .png, .gif, .mp3, .mp4, .mov, .wav, .txt, .md)
     - other data types are opened with external apps if they are able to handle it
 - edit profile information and privacy settings
+- add grid view for calls, make own video movable
 
 ### Changed
 - improve conversation list design and dark/light theming (@AndyScherzinger)

+ 115 - 0
app/src/main/java/com/nextcloud/talk/adapters/ParticipantDisplayItem.java

@@ -0,0 +1,115 @@
+package com.nextcloud.talk.adapters;
+
+import org.webrtc.EglBase;
+import org.webrtc.MediaStream;
+
+public class ParticipantDisplayItem {
+    private String userId;
+    private String session;
+    private String nick;
+    private String urlForAvatar;
+    private MediaStream mediaStream;
+    private String streamType;
+    private boolean streamEnabled;
+    private EglBase rootEglBase;
+    private boolean isAudioEnabled;
+
+    public ParticipantDisplayItem(String userId, String session, String nick, String urlForAvatar, MediaStream mediaStream, String streamType, boolean streamEnabled, EglBase rootEglBase) {
+        this.userId = userId;
+        this.session = session;
+        this.nick = nick;
+        this.urlForAvatar = urlForAvatar;
+        this.mediaStream = mediaStream;
+        this.streamType = streamType;
+        this.streamEnabled = streamEnabled;
+        this.rootEglBase = rootEglBase;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getSession() {
+        return session;
+    }
+
+    public void setSession(String session) {
+        this.session = session;
+    }
+
+    public String getNick() {
+        return nick;
+    }
+
+    public void setNick(String nick) {
+        this.nick = nick;
+    }
+
+    public String getUrlForAvatar() {
+        return urlForAvatar;
+    }
+
+    public void setUrlForAvatar(String urlForAvatar) {
+        this.urlForAvatar = urlForAvatar;
+    }
+
+    public MediaStream getMediaStream() {
+        return mediaStream;
+    }
+
+    public void setMediaStream(MediaStream mediaStream) {
+        this.mediaStream = mediaStream;
+    }
+
+    public String getStreamType() {
+        return streamType;
+    }
+
+    public void setStreamType(String streamType) {
+        this.streamType = streamType;
+    }
+
+    public boolean isStreamEnabled() {
+        return streamEnabled;
+    }
+
+    public void setStreamEnabled(boolean streamEnabled) {
+        this.streamEnabled = streamEnabled;
+    }
+
+    public EglBase getRootEglBase() {
+        return rootEglBase;
+    }
+
+    public void setRootEglBase(EglBase rootEglBase) {
+        this.rootEglBase = rootEglBase;
+    }
+
+    public boolean isAudioEnabled() {
+        return isAudioEnabled;
+    }
+
+    public void setAudioEnabled(boolean audioEnabled) {
+        isAudioEnabled = audioEnabled;
+    }
+
+    @Override
+    public String toString() {
+        return "ParticipantSession{" +
+                "userId='" + userId + '\'' +
+                ", session='" + session + '\'' +
+                ", nick='" + nick + '\'' +
+                ", urlForAvatar='" + urlForAvatar + '\'' +
+                ", mediaStream=" + mediaStream +
+                ", streamType='" + streamType + '\'' +
+                ", streamEnabled=" + streamEnabled +
+                ", rootEglBase=" + rootEglBase +
+                '}';
+    }
+}
+
+

+ 154 - 0
app/src/main/java/com/nextcloud/talk/adapters/ParticipantsAdapter.java

@@ -0,0 +1,154 @@
+package com.nextcloud.talk.adapters;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.drawee.interfaces.DraweeController;
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.nextcloud.talk.R;
+import com.nextcloud.talk.utils.DisplayUtils;
+
+import org.webrtc.MediaStream;
+import org.webrtc.RendererCommon;
+import org.webrtc.SurfaceViewRenderer;
+import org.webrtc.VideoTrack;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+public class ParticipantsAdapter extends BaseAdapter {
+
+    private static final String TAG = "ParticipantsAdapter";
+
+    private final Context mContext;
+    private final ArrayList<ParticipantDisplayItem> participantDisplayItems;
+    private final RelativeLayout gridViewWrapper;
+    private final LinearLayout callInfosLinearLayout;
+    private final int columns;
+    private final boolean isVoiceOnlyCall;
+
+    public ParticipantsAdapter(Context mContext,
+                               Map<String, ParticipantDisplayItem> participantDisplayItems,
+                               RelativeLayout gridViewWrapper,
+                               LinearLayout linearLayout,
+                               int columns,
+                               boolean isVoiceOnlyCall) {
+        this.mContext = mContext;
+        this.gridViewWrapper = gridViewWrapper;
+        this.callInfosLinearLayout = linearLayout;
+        this.columns = columns;
+        this.isVoiceOnlyCall = isVoiceOnlyCall;
+
+        this.participantDisplayItems = new ArrayList<>();
+        this.participantDisplayItems.addAll(participantDisplayItems.values());
+    }
+
+
+    @Override
+    public int getCount() {
+        return participantDisplayItems.size();
+    }
+
+    @Override
+    public ParticipantDisplayItem getItem(int position) {
+        return participantDisplayItems.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return 0;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        ParticipantDisplayItem participantDisplayItem = getItem(position);
+
+        SurfaceViewRenderer surfaceViewRenderer;
+        if (convertView == null) {
+            convertView = LayoutInflater.from(mContext).inflate(R.layout.call_item, parent, false);
+            convertView.setVisibility(View.VISIBLE);
+
+            surfaceViewRenderer = convertView.findViewById(R.id.surface_view);
+            try {
+                surfaceViewRenderer.setMirror(false);
+                surfaceViewRenderer.init(participantDisplayItem.getRootEglBase().getEglBaseContext(), null);
+                surfaceViewRenderer.setZOrderMediaOverlay(false);
+                // disabled because it causes some devices to crash
+                surfaceViewRenderer.setEnableHardwareScaler(false);
+                surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
+            } catch (Exception e) {
+                Log.e(TAG, "error while initializing surfaceViewRenderer", e);
+            }
+        } else {
+            surfaceViewRenderer = convertView.findViewById(R.id.surface_view);
+        }
+
+        ViewGroup.LayoutParams layoutParams = convertView.getLayoutParams();
+        layoutParams.height = scaleGridViewItemHeight();
+        convertView.setLayoutParams(layoutParams);
+
+
+        TextView nickTextView = convertView.findViewById(R.id.peer_nick_text_view);
+        SimpleDraweeView imageView = convertView.findViewById(R.id.avatarImageView);
+
+        MediaStream mediaStream = participantDisplayItem.getMediaStream();
+        if (hasVideoStream(participantDisplayItem, mediaStream)) {
+            VideoTrack videoTrack = mediaStream.videoTracks.get(0);
+            videoTrack.addSink(surfaceViewRenderer);
+            imageView.setVisibility(View.INVISIBLE);
+            surfaceViewRenderer.setVisibility(View.VISIBLE);
+            nickTextView.setVisibility(View.GONE);
+        } else {
+            imageView.setVisibility(View.VISIBLE);
+            surfaceViewRenderer.setVisibility(View.INVISIBLE);
+            nickTextView.setVisibility(View.VISIBLE);
+            nickTextView.setText(participantDisplayItem.getNick());
+
+            imageView.setController(null);
+            DraweeController draweeController = Fresco.newDraweeControllerBuilder()
+                    .setOldController(imageView.getController())
+                    .setImageRequest(DisplayUtils.getImageRequestForUrl(participantDisplayItem.getUrlForAvatar(), null))
+                    .build();
+            imageView.setController(draweeController);
+        }
+
+        ImageView audioOffView = convertView.findViewById(R.id.remote_audio_off);
+        if (!participantDisplayItem.isAudioEnabled()) {
+            audioOffView.setVisibility(View.VISIBLE);
+        } else {
+            audioOffView.setVisibility(View.INVISIBLE);
+        }
+        return convertView;
+    }
+
+    private boolean hasVideoStream(ParticipantDisplayItem participantDisplayItem, MediaStream mediaStream) {
+        return mediaStream != null && mediaStream.videoTracks != null && mediaStream.videoTracks.size() > 0 && participantDisplayItem.isStreamEnabled();
+    }
+
+    private int scaleGridViewItemHeight() {
+        int headerHeight = 0;
+        if (callInfosLinearLayout.getVisibility() == View.VISIBLE && isVoiceOnlyCall) {
+            headerHeight = callInfosLinearLayout.getHeight();
+        }
+        int itemHeight = (gridViewWrapper.getHeight() - headerHeight) / getRowsCount(getCount());
+        return itemHeight;
+    }
+
+    private int getRowsCount(int items) {
+        int rows = (int) Math.ceil((double) items / (double) columns);
+        if (rows == 0) {
+            rows = 1;
+        }
+        return rows;
+    }
+}

+ 259 - 269
app/src/main/java/com/nextcloud/talk/controllers/CallController.java

@@ -34,12 +34,15 @@ import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
+import android.widget.GridView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
@@ -51,11 +54,11 @@ import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 
 import com.bluelinelabs.logansquare.LoganSquare;
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.drawee.interfaces.DraweeController;
 import com.facebook.drawee.view.SimpleDraweeView;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.activities.MagicCallActivity;
+import com.nextcloud.talk.adapters.ParticipantDisplayItem;
+import com.nextcloud.talk.adapters.ParticipantsAdapter;
 import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.controllers.base.BaseController;
@@ -143,6 +146,7 @@ import javax.inject.Inject;
 import autodagger.AutoInjector;
 import butterknife.BindView;
 import butterknife.OnClick;
+import butterknife.OnItemClick;
 import butterknife.OnLongClick;
 import io.reactivex.Observable;
 import io.reactivex.Observer;
@@ -180,8 +184,8 @@ public class CallController extends BaseController {
     SurfaceViewRenderer pipVideoView;
     @BindView(R.id.controllerCallLayout)
     RelativeLayout controllerCallLayout;
-    @BindView(R.id.remote_renderers_layout)
-    LinearLayout remoteRenderersLayout;
+    @BindView(R.id.gridview)
+    GridView gridView;
 
     @BindView(R.id.callControlsLinearLayout)
     LinearLayout callControls;
@@ -201,6 +205,9 @@ public class CallController extends BaseController {
     @BindView(R.id.callConversationNameTextView)
     TextView callConversationNameTextView;
 
+    @BindView(R.id.selfVideoView)
+    FrameLayout selfVideoView;
+
     @BindView(R.id.callStateRelativeLayoutView)
     RelativeLayout callStateView;
 
@@ -264,7 +271,6 @@ public class CallController extends BaseController {
     // push to talk
     private boolean isPTTActive = false;
     private PulseAnimation pulseAnimation;
-    private View.OnClickListener videoOnClickListener;
 
     private String baseUrl;
     private String roomId;
@@ -286,6 +292,9 @@ public class CallController extends BaseController {
 
     private MediaPlayer mediaPlayer;
 
+    private Map<String, ParticipantDisplayItem> participantDisplayItems;
+    private ParticipantsAdapter participantsAdapter;
+
     @Parcel
     public enum CallStatus {
         CONNECTING, CALLING_TIMEOUT, JOINED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING, PUBLISHER_FAILED
@@ -345,19 +354,18 @@ public class CallController extends BaseController {
         }
     }
 
+    @SuppressLint("ClickableViewAccessibility")
     @Override
     protected void onViewBound(@NonNull View view) {
         super.onViewBound(view);
 
         microphoneControlButton.setOnTouchListener(new MicrophoneButtonTouchListener());
-        videoOnClickListener = new VideoClickListener();
 
         pulseAnimation = PulseAnimation.create().with(microphoneControlButton)
                 .setDuration(310)
                 .setRepeatCount(PulseAnimation.INFINITE)
                 .setRepeatMode(PulseAnimation.REVERSE);
 
-        setPipVideoViewDimensions();
 
         try {
             cache.evictAll();
@@ -368,7 +376,7 @@ public class CallController extends BaseController {
         callControls.setZ(100.0f);
         basicInitialization();
         initViews();
-
+        initPipView();
         initiateCall();
     }
 
@@ -467,7 +475,11 @@ public class CallController extends BaseController {
                 });
     }
 
+
+    @SuppressLint("ClickableViewAccessibility")
     private void initViews() {
+        participantDisplayItems = new HashMap<>();
+
         if (isVoiceOnlyCall) {
             callControlEnableSpeaker.setVisibility(View.VISIBLE);
             cameraSwitchButton.setVisibility(View.GONE);
@@ -476,7 +488,7 @@ public class CallController extends BaseController {
 
             RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
             params.addRule(RelativeLayout.BELOW, R.id.callInfosLinearLayout);
-            remoteRenderersLayout.setLayoutParams(params);
+            gridView.setLayoutParams(params);
         } else {
             callControlEnableSpeaker.setVisibility(View.GONE);
             if (cameraEnumerator.getDeviceNames().length < 2) {
@@ -488,7 +500,77 @@ public class CallController extends BaseController {
             // disabled because it causes some devices to crash
             pipVideoView.setEnableHardwareScaler(false);
             pipVideoView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
+
+            pipVideoView.setOnTouchListener(new SelfVideoTouchListener());
+
+        }
+
+        gridView.setOnTouchListener(new View.OnTouchListener() {
+            public boolean onTouch(View v, MotionEvent me) {
+                int action = me.getActionMasked();
+                if (action == MotionEvent.ACTION_DOWN) {
+                    showCallControls();
+                }
+                return true;
+            }
+        });
+
+        initGridAdapter();
+    }
+
+    private void initGridAdapter() {
+        GridView gridView = conversationView.findViewById(R.id.gridview);
+
+        int columns;
+        int participantsInGrid = participantDisplayItems.size();
+        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+            if (participantsInGrid > 8) {
+                columns = 3;
+            } else if (participantsInGrid > 2) {
+                columns = 2;
+            } else {
+                columns = 1;
+            }
+        } else {
+            if (participantsInGrid > 8) {
+                columns = 4;
+            } else if (participantsInGrid > 2) {
+                columns = 3;
+            } else if (participantsInGrid > 1) {
+                columns = 2;
+            } else {
+                columns = 1;
+            }
         }
+
+        gridView.setNumColumns(columns);
+
+        RelativeLayout gridViewWrapper = conversationView.findViewById(R.id.conversationRelativeLayoutView);
+        gridViewWrapper.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                gridViewWrapper.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                int height = gridViewWrapper.getMeasuredHeight();
+                gridView.setMinimumHeight(height);
+            }
+        });
+
+        LinearLayout callInfosLinearLayout = conversationView.findViewById(R.id.callInfosLinearLayout);
+        callInfosLinearLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                callInfosLinearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+            }
+        });
+
+        participantsAdapter = new ParticipantsAdapter(
+                this.getActivity(),
+                participantDisplayItems,
+                gridViewWrapper,
+                callInfosLinearLayout,
+                columns,
+                isVoiceOnlyCall);
+        gridView.setAdapter(participantsAdapter);
     }
 
 
@@ -534,7 +616,7 @@ public class CallController extends BaseController {
                 fetchSignalingSettings();
             }
         } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(),
-                PERMISSIONS_CALL)) {
+                                                                                                  PERMISSIONS_CALL)) {
             checkIfSomeAreApproved();
         }
 
@@ -744,7 +826,7 @@ public class CallController extends BaseController {
             }
 
         } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(),
-                PERMISSIONS_MICROPHONE)) {
+                                                                                                  PERMISSIONS_MICROPHONE)) {
             // Microphone permission is permanently denied so we cannot request it normally.
 
             OpenAppDetailsDialogFragment.show(
@@ -787,7 +869,7 @@ public class CallController extends BaseController {
 
             toggleMedia(videoOn, true);
         } else if (getActivity() != null && EffortlessPermissions.somePermissionPermanentlyDenied(getActivity(),
-                PERMISSIONS_CAMERA)) {
+                                                                                                  PERMISSIONS_CAMERA)) {
             // Camera permission is permanently denied so we cannot request it normally.
             OpenAppDetailsDialogFragment.show(
                     R.string.nc_camera_permission_permanently_denied,
@@ -803,7 +885,7 @@ public class CallController extends BaseController {
 
     }
 
-    @OnClick({R.id.call_control_switch_camera, R.id.pip_video_view})
+    @OnClick({R.id.call_control_switch_camera})
     public void switchCamera() {
         CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) videoCapturer;
         if (cameraVideoCapturer != null) {
@@ -1046,7 +1128,7 @@ public class CallController extends BaseController {
                             if (!conversationUser.getUserId().equals("?")) {
                                 try {
                                     userUtils.createOrUpdateUser(null, null, null, null, null, null, null,
-                                            conversationUser.getId(), null, null, LoganSquare.serialize(externalSignalingServer))
+                                                                 conversationUser.getId(), null, null, LoganSquare.serialize(externalSignalingServer))
                                             .subscribeOn(Schedulers.io())
                                             .subscribe();
                                 } catch (IOException exception) {
@@ -1059,11 +1141,11 @@ public class CallController extends BaseController {
                                      i++) {
                                     iceServer = signalingSettingsOverall.getOcs().getSettings().getStunServers().get(i);
                                     if (TextUtils.isEmpty(iceServer.getUsername()) || TextUtils.isEmpty(iceServer
-                                            .getCredential())) {
+                                                                                                                .getCredential())) {
                                         iceServers.add(new PeerConnection.IceServer(iceServer.getUrl()));
                                     } else {
                                         iceServers.add(new PeerConnection.IceServer(iceServer.getUrl(),
-                                                iceServer.getUsername(), iceServer.getCredential()));
+                                                                                    iceServer.getUsername(), iceServer.getCredential()));
                                     }
                                 }
                             }
@@ -1074,11 +1156,11 @@ public class CallController extends BaseController {
                                     iceServer = signalingSettingsOverall.getOcs().getSettings().getTurnServers().get(i);
                                     for (int j = 0; j < iceServer.getUrls().size(); j++) {
                                         if (TextUtils.isEmpty(iceServer.getUsername()) || TextUtils.isEmpty(iceServer
-                                                .getCredential())) {
+                                                                                                                    .getCredential())) {
                                             iceServers.add(new PeerConnection.IceServer(iceServer.getUrls().get(j)));
                                         } else {
                                             iceServers.add(new PeerConnection.IceServer(iceServer.getUrls().get(j),
-                                                    iceServer.getUsername(), iceServer.getCredential()));
+                                                                                        iceServer.getUsername(), iceServer.getCredential()));
                                         }
                                     }
                                 }
@@ -1154,7 +1236,7 @@ public class CallController extends BaseController {
 
         if (TextUtils.isEmpty(callSession)) {
             ncApi.joinRoom(credentials, ApiUtils.getUrlForSettingMyselfAsActiveParticipant(baseUrl,
-                    roomToken), conversationPassword)
+                                                                                           roomToken), conversationPassword)
                     .subscribeOn(Schedulers.io())
                     .observeOn(AndroidSchedulers.mainThread())
                     .retry(3)
@@ -1207,7 +1289,7 @@ public class CallController extends BaseController {
         }
 
         ncApi.joinCall(credentials,
-                ApiUtils.getUrlForCall(baseUrl, roomToken), inCallFlag)
+                       ApiUtils.getUrlForCall(baseUrl, roomToken), inCallFlag)
                 .subscribeOn(Schedulers.io())
                 .retry(3)
                 .observeOn(AndroidSchedulers.mainThread())
@@ -1381,7 +1463,7 @@ public class CallController extends BaseController {
         }
     }
 
-    @OnClick({R.id.pip_video_view, R.id.remote_renderers_layout})
+    @OnItemClick({R.id.gridview})
     public void showCallControls() {
         animateCallControls(true, 0);
     }
@@ -1414,10 +1496,10 @@ public class CallController extends BaseController {
             processUsersInRoom((List<HashMap<String, Object>>) signaling.getMessageWrapper());
         } else if ("message".equals(messageType)) {
             NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
-                    NCSignalingMessage.class);
+                                                                      NCSignalingMessage.class);
             processMessage(ncSignalingMessage);
         } else {
-            Log.d(TAG, "Something went very very wrong");
+            Log.e(TAG, "unexpected message type when receiving signaling message");
         }
     }
 
@@ -1425,7 +1507,7 @@ public class CallController extends BaseController {
         if (ncSignalingMessage.getRoomType().equals("video") || ncSignalingMessage.getRoomType().equals("screen")) {
             MagicPeerConnectionWrapper magicPeerConnectionWrapper =
                     getPeerConnectionWrapperForSessionIdAndType(ncSignalingMessage.getFrom(),
-                            ncSignalingMessage.getRoomType(), false);
+                                                                ncSignalingMessage.getRoomType(), false);
 
             String type = null;
             if (ncSignalingMessage.getPayload() != null && ncSignalingMessage.getPayload().getType() != null) {
@@ -1446,7 +1528,7 @@ public class CallController extends BaseController {
 
                         String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec
                                 (ncSignalingMessage.getPayload().getSdp(),
-                                        "H264", false);
+                                 "H264", false);
 
                         sessionDescriptionWithPreferredCodec = new SessionDescription(
                                 SessionDescription.Type.fromCanonicalForm(type),
@@ -1454,13 +1536,13 @@ public class CallController extends BaseController {
 
                         if (magicPeerConnectionWrapper.getPeerConnection() != null) {
                             magicPeerConnectionWrapper.getPeerConnection().setRemoteDescription(magicPeerConnectionWrapper
-                                    .getMagicSdpObserver(), sessionDescriptionWithPreferredCodec);
+                                                                                                        .getMagicSdpObserver(), sessionDescriptionWithPreferredCodec);
                         }
                         break;
                     case "candidate":
                         NCIceCandidate ncIceCandidate = ncSignalingMessage.getPayload().getIceCandidate();
                         IceCandidate iceCandidate = new IceCandidate(ncIceCandidate.getSdpMid(),
-                                ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate());
+                                                                     ncIceCandidate.getSdpMLineIndex(), ncIceCandidate.getCandidate());
                         magicPeerConnectionWrapper.addCandidate(iceCandidate);
                         break;
                     case "endOfCandidates":
@@ -1471,7 +1553,7 @@ public class CallController extends BaseController {
                 }
             }
         } else {
-            Log.d(TAG, "Something went very very wrong");
+            Log.e(TAG, "unexpected RoomType while processing NCSignalingMessage");
         }
     }
 
@@ -1658,6 +1740,7 @@ public class CallController extends BaseController {
     }
 
     private void getPeersForCall() {
+        Log.d(TAG, "getPeersForCall");
         ncApi.getPeersForCall(credentials, ApiUtils.getUrlForCall(baseUrl, roomToken))
                 .subscribeOn(Schedulers.io())
                 .subscribe(new Observer<ParticipantsOverall>() {
@@ -1671,15 +1754,12 @@ public class CallController extends BaseController {
                         participantMap = new HashMap<>();
                         for (Participant participant : participantsOverall.getOcs().getData()) {
                             participantMap.put(participant.getSessionId(), participant);
-                            if (getActivity() != null) {
-                                getActivity().runOnUiThread(() -> setupAvatarForSession(participant.getSessionId()));
-                            }
                         }
                     }
 
                     @Override
                     public void onError(Throwable e) {
-
+                        Log.e(TAG, "error while executing getPeersForCall", e);
                     }
 
                     @Override
@@ -1711,18 +1791,18 @@ public class CallController extends BaseController {
         } else {
             if (hasMCU && publisher) {
                 magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory,
-                        iceServers, sdpConstraintsForMCU, sessionId, callSession, localMediaStream, true, true, type);
+                                                                            iceServers, sdpConstraintsForMCU, sessionId, callSession, localMediaStream, true, true, type);
 
             } else if (hasMCU) {
                 magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory,
-                        iceServers, sdpConstraints, sessionId, callSession, null, false, true, type);
+                                                                            iceServers, sdpConstraints, sessionId, callSession, null, false, true, type);
             } else {
                 if (!"screen".equals(type)) {
                     magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory,
-                            iceServers, sdpConstraints, sessionId, callSession, localMediaStream, false, false, type);
+                                                                                iceServers, sdpConstraints, sessionId, callSession, localMediaStream, false, false, type);
                 } else {
                     magicPeerConnectionWrapper = new MagicPeerConnectionWrapper(peerConnectionFactory,
-                            iceServers, sdpConstraints, sessionId, callSession, null, false, false, type);
+                                                                                iceServers, sdpConstraints, sessionId, callSession, null, false, false, type);
                 }
             }
 
@@ -1756,9 +1836,10 @@ public class CallController extends BaseController {
                 magicPeerConnectionWrapper = magicPeerConnectionWrappers.get(i);
                 if (magicPeerConnectionWrapper.getSessionId().equals(sessionId)) {
                     if (magicPeerConnectionWrapper.getVideoStreamType().equals("screen") || !justScreen) {
-                        MagicPeerConnectionWrapper finalMagicPeerConnectionWrapper = magicPeerConnectionWrapper;
-                        getActivity().runOnUiThread(() -> removeMediaStream(sessionId + "+" +
-                                finalMagicPeerConnectionWrapper.getVideoStreamType()));
+
+
+                        // TODO runOnUiThread not necessary???
+                        getActivity().runOnUiThread(() -> removeMediaStream(sessionId));
                         deleteMagicPeerConnection(magicPeerConnectionWrapper);
                     }
                 }
@@ -1767,15 +1848,9 @@ public class CallController extends BaseController {
     }
 
     private void removeMediaStream(String sessionId) {
-        if (remoteRenderersLayout != null && remoteRenderersLayout.getChildCount() > 0) {
-            RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(sessionId);
-            if (relativeLayout != null) {
-                SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id.surface_view);
-                surfaceViewRenderer.release();
-                remoteRenderersLayout.removeView(relativeLayout);
-                remoteRenderersLayout.invalidate();
-            }
-        }
+        Log.d(TAG, "removeMediaStream");
+        participantDisplayItems.remove(sessionId);
+        initGridAdapter();
 
         if (callControls != null) {
             callControls.setZ(100.0f);
@@ -1785,63 +1860,76 @@ public class CallController extends BaseController {
     @Subscribe(threadMode = ThreadMode.MAIN)
     public void onMessageEvent(ConfigurationChangeEvent configurationChangeEvent) {
         powerManagerUtils.setOrientation(Objects.requireNonNull(getResources()).getConfiguration().orientation);
+        initGridAdapter();
+        initPipView();
+    }
 
+    private void initPipView() {
+        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) pipVideoView.getLayoutParams();
 
-        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            remoteRenderersLayout.setOrientation(LinearLayout.HORIZONTAL);
-        } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
-            remoteRenderersLayout.setOrientation(LinearLayout.VERTICAL);
-        }
+        DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics();
+        int screenWidthPx = displayMetrics.widthPixels;
 
-        setPipVideoViewDimensions();
-    }
+        int screenWidthDp = (int) DisplayUtils.convertPixelToDp(screenWidthPx, getApplicationContext());
 
-    private void setPipVideoViewDimensions() {
-        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) pipVideoView.getLayoutParams();
+        float newXafterRotate = 0;
+        float newYafterRotate;
+        if (callInfosLinearLayout.getVisibility() == View.VISIBLE) {
+            newYafterRotate = 250;
+        } else {
+            newYafterRotate = 20;
+        }
 
         if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            remoteRenderersLayout.setOrientation(LinearLayout.HORIZONTAL);
             layoutParams.height = (int) getResources().getDimension(R.dimen.large_preview_dimension);
             layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
-            pipVideoView.setLayoutParams(layoutParams);
+            newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.large_preview_dimension) * 0.8);
+
         } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
-            remoteRenderersLayout.setOrientation(LinearLayout.VERTICAL);
             layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT;
             layoutParams.width = (int) getResources().getDimension(R.dimen.large_preview_dimension);
-            pipVideoView.setLayoutParams(layoutParams);
+            newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.large_preview_dimension) * 0.5);
         }
+        pipVideoView.setLayoutParams(layoutParams);
+
+        int newXafterRotatePx = (int) DisplayUtils.convertDpToPixel(newXafterRotate, getApplicationContext());
+        selfVideoView.setY(newYafterRotate);
+        selfVideoView.setX(newXafterRotatePx);
     }
 
     @Subscribe(threadMode = ThreadMode.MAIN)
     public void onMessageEvent(PeerConnectionEvent peerConnectionEvent) {
+        String sessionId = peerConnectionEvent.getSessionId();
+
         if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType
-                .PEER_CLOSED)) {
-            endPeerConnection(peerConnectionEvent.getSessionId(), peerConnectionEvent.getVideoStreamType().equals("screen"));
+                                                                            .PEER_CLOSED)) {
+            endPeerConnection(sessionId, peerConnectionEvent.getVideoStreamType().equals("screen"));
         } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
-                .PeerConnectionEventType.SENSOR_FAR) ||
+                                                                                   .PeerConnectionEventType.SENSOR_FAR) ||
                 peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
-                        .PeerConnectionEventType.SENSOR_NEAR)) {
+                                                                                .PeerConnectionEventType.SENSOR_NEAR)) {
 
             if (!isVoiceOnlyCall) {
                 boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
-                        .PeerConnectionEventType.SENSOR_FAR) && videoOn;
+                                                                                                      .PeerConnectionEventType.SENSOR_FAR) && videoOn;
                 if (getActivity() != null && EffortlessPermissions.hasPermissions(getActivity(), PERMISSIONS_CAMERA) &&
                         (currentCallStatus.equals(CallStatus.CONNECTING) || isConnectionEstablished()) && videoOn
                         && enableVideo != localVideoTrack.enabled()) {
                     toggleMedia(enableVideo, true);
                 }
             }
-        } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
-                .PeerConnectionEventType.NICK_CHANGE)) {
-            gotNick(peerConnectionEvent.getSessionId(), peerConnectionEvent.getNick(), peerConnectionEvent.getVideoStreamType());
-        } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
-                .PeerConnectionEventType.VIDEO_CHANGE) && !isVoiceOnlyCall) {
-            gotAudioOrVideoChange(true, peerConnectionEvent.getSessionId() + "+" + peerConnectionEvent.getVideoStreamType(),
-                    peerConnectionEvent.getChangeValue());
-        } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent
-                .PeerConnectionEventType.AUDIO_CHANGE)) {
-            gotAudioOrVideoChange(false, peerConnectionEvent.getSessionId() + "+" + peerConnectionEvent.getVideoStreamType(),
-                    peerConnectionEvent.getChangeValue());
+        } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType.NICK_CHANGE)) {
+            participantDisplayItems.get(sessionId).setNick(peerConnectionEvent.getNick());
+            participantsAdapter.notifyDataSetChanged();
+
+        } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType.VIDEO_CHANGE) && !isVoiceOnlyCall) {
+            participantDisplayItems.get(sessionId).setStreamEnabled(peerConnectionEvent.getChangeValue());
+            participantsAdapter.notifyDataSetChanged();
+
+        } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType.AUDIO_CHANGE)) {
+            participantDisplayItems.get(sessionId).setAudioEnabled(peerConnectionEvent.getChangeValue());
+            participantsAdapter.notifyDataSetChanged();
+
         } else if (peerConnectionEvent.getPeerConnectionEventType().equals(PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED)) {
             currentCallStatus = CallStatus.PUBLISHER_FAILED;
             webSocketClient.clearResumeId();
@@ -1894,11 +1982,20 @@ public class CallController extends BaseController {
     @Subscribe(threadMode = ThreadMode.MAIN)
     public void onMessageEvent(MediaStreamEvent mediaStreamEvent) {
         if (mediaStreamEvent.getMediaStream() != null) {
-            setupVideoStreamForLayout(mediaStreamEvent.getMediaStream(), mediaStreamEvent.getSession(),
-                    mediaStreamEvent.getMediaStream().videoTracks != null
-                            && mediaStreamEvent.getMediaStream().videoTracks.size() > 0, mediaStreamEvent.getVideoStreamType());
+            boolean hasAtLeastOneVideoStream = mediaStreamEvent.getMediaStream().videoTracks != null
+                    && mediaStreamEvent.getMediaStream().videoTracks.size() > 0;
+
+            setupVideoStreamForLayout(
+                    mediaStreamEvent.getMediaStream(),
+                    mediaStreamEvent.getSession(),
+                    hasAtLeastOneVideoStream,
+                    mediaStreamEvent.getVideoStreamType());
         } else {
-            setupVideoStreamForLayout(null, mediaStreamEvent.getSession(), false, mediaStreamEvent.getVideoStreamType());
+            setupVideoStreamForLayout(
+                    null,
+                    mediaStreamEvent.getSession(),
+                    false,
+                    mediaStreamEvent.getVideoStreamType());
         }
     }
 
@@ -1949,7 +2046,7 @@ public class CallController extends BaseController {
             }
 
             ncApi.sendSignalingMessages(credentials, ApiUtils.getUrlForSignaling(baseUrl, urlToken),
-                    strings.toString())
+                                        strings.toString())
                     .retry(3)
                     .subscribeOn(Schedulers.io())
                     .subscribe(new Observer<SignalingOverall>() {
@@ -1965,7 +2062,7 @@ public class CallController extends BaseController {
                                     try {
                                         receivedSignalingMessage(signalingOverall.getOcs().getSignalings().get(i));
                                     } catch (IOException e) {
-                                        e.printStackTrace();
+                                        Log.e(TAG, "", e);
                                     }
                                 }
                             }
@@ -1973,6 +2070,7 @@ public class CallController extends BaseController {
 
                         @Override
                         public void onError(Throwable e) {
+                            Log.e(TAG, "", e);
                         }
 
                         @Override
@@ -1991,161 +2089,49 @@ public class CallController extends BaseController {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 
         EffortlessPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults,
-                this);
-    }
-
-    private void setupAvatarForSession(String session) {
-        if (remoteRenderersLayout != null) {
-            RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(session + "+video");
-            if (relativeLayout != null) {
-                SimpleDraweeView avatarImageView = relativeLayout.findViewById(R.id.avatarImageView);
-
-                String userId;
-                String displayName;
-
-                if (hasMCU) {
-                    userId = webSocketClient.getUserIdForSession(session);
-                    displayName = getPeerConnectionWrapperForSessionIdAndType(session, "video", false).getNick();
-                } else {
-                    userId = participantMap.get(session).getUserId();
-                    displayName = getPeerConnectionWrapperForSessionIdAndType(session, "video", false).getNick();
-                }
-
-                if (!TextUtils.isEmpty(userId) || !TextUtils.isEmpty(displayName)) {
-
-                    if (getActivity() != null) {
-                        avatarImageView.setController(null);
-
-                        String urlForAvatar;
-                        if (!TextUtils.isEmpty(userId)) {
-                            urlForAvatar = ApiUtils.getUrlForAvatarWithName(baseUrl,
-                                    userId,
-                                    R.dimen.avatar_size_big);
-                        } else {
-                            urlForAvatar = ApiUtils.getUrlForAvatarWithNameForGuests(baseUrl,
-                                    displayName,
-                                    R.dimen.avatar_size_big);
-                        }
-
-                        DraweeController draweeController = Fresco.newDraweeControllerBuilder()
-                                .setOldController(avatarImageView.getController())
-                                .setImageRequest(DisplayUtils.getImageRequestForUrl(urlForAvatar, null))
-                                .build();
-                        avatarImageView.setController(draweeController);
-                    }
-                }
-            }
-        }
+                                                         this);
     }
 
-    private void setupVideoStreamForLayout(@Nullable MediaStream mediaStream, String session, boolean enable, String videoStreamType) {
-        boolean isInitialLayoutSetupForPeer = false;
-        if (remoteRenderersLayout.findViewWithTag(session) == null) {
-            setupNewPeerLayout(session, videoStreamType);
-            isInitialLayoutSetupForPeer = true;
+    private void setupVideoStreamForLayout(@Nullable MediaStream mediaStream, String session, boolean videoStreamEnabled, String videoStreamType) {
+        String nick;
+        if (hasExternalSignalingServer) {
+            nick = webSocketClient.getDisplayNameForSession(session);
+        } else {
+            nick = getPeerConnectionWrapperForSessionIdAndType(session, videoStreamType, false).getNick();
         }
 
-        RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(session + "+" + videoStreamType);
-        SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id.surface_view);
-        SimpleDraweeView imageView = relativeLayout.findViewById(R.id.avatarImageView);
-
-        if (mediaStream != null && mediaStream.videoTracks != null && mediaStream.videoTracks.size() > 0 && enable) {
-            VideoTrack videoTrack = mediaStream.videoTracks.get(0);
-
-            videoTrack.addSink(surfaceViewRenderer);
-
-            imageView.setVisibility(View.INVISIBLE);
-            surfaceViewRenderer.setVisibility(View.VISIBLE);
+        String userId;
+        if (hasMCU) {
+            userId = webSocketClient.getUserIdForSession(session);
         } else {
-            imageView.setVisibility(View.VISIBLE);
-            surfaceViewRenderer.setVisibility(View.INVISIBLE);
-
-            if (isInitialLayoutSetupForPeer && isVoiceOnlyCall) {
-                gotAudioOrVideoChange(true, session, false);
-            }
+            userId = participantMap.get(session).getUserId();
         }
 
+        String urlForAvatar;
+        if (!TextUtils.isEmpty(userId)) {
+            urlForAvatar = ApiUtils.getUrlForAvatarWithName(baseUrl,
+                                                            userId,
+                                                            R.dimen.avatar_size_big);
+        } else {
+            urlForAvatar = ApiUtils.getUrlForAvatarWithNameForGuests(baseUrl,
+                                                                     nick,
+                                                                     R.dimen.avatar_size_big);
+        }
+
+        ParticipantDisplayItem participantDisplayItem = new ParticipantDisplayItem(userId,
+                                                                                   session,
+                                                                                   nick,
+                                                                                   urlForAvatar,
+                                                                                   mediaStream,
+                                                                                   videoStreamType,
+                                                                                   videoStreamEnabled,
+                                                                                   rootEglBase);
+        participantDisplayItems.put(session, participantDisplayItem);
+
+        initGridAdapter();
         callControls.setZ(100.0f);
     }
 
-    private void gotAudioOrVideoChange(boolean video, String sessionId, boolean change) {
-        RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(sessionId);
-        if (relativeLayout != null) {
-            ImageView imageView;
-            SimpleDraweeView avatarImageView = relativeLayout.findViewById(R.id.avatarImageView);
-            SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id.surface_view);
-
-            if (video) {
-                imageView = relativeLayout.findViewById(R.id.remote_video_off);
-
-                if (change) {
-                    avatarImageView.setVisibility(View.INVISIBLE);
-                    surfaceViewRenderer.setVisibility(View.VISIBLE);
-                } else {
-                    avatarImageView.setVisibility(View.VISIBLE);
-                    surfaceViewRenderer.setVisibility(View.INVISIBLE);
-                }
-            } else {
-                imageView = relativeLayout.findViewById(R.id.remote_audio_off);
-            }
-
-            if (change && imageView.getVisibility() != View.INVISIBLE) {
-                imageView.setVisibility(View.INVISIBLE);
-            } else if (!change && imageView.getVisibility() != View.VISIBLE) {
-                imageView.setVisibility(View.VISIBLE);
-            }
-        }
-    }
-
-    private void setupNewPeerLayout(String session, String type) {
-        if (remoteRenderersLayout.findViewWithTag(session + "+" + type) == null && getActivity() != null) {
-            getActivity().runOnUiThread(() -> {
-                RelativeLayout relativeLayout = (RelativeLayout)
-                        getActivity().getLayoutInflater().inflate(R.layout.call_item, remoteRenderersLayout,
-                                false);
-                relativeLayout.setTag(session + "+" + type);
-                SurfaceViewRenderer surfaceViewRenderer = relativeLayout.findViewById(R.id
-                        .surface_view);
-
-                surfaceViewRenderer.setMirror(false);
-                surfaceViewRenderer.init(rootEglBase.getEglBaseContext(), null);
-                surfaceViewRenderer.setZOrderMediaOverlay(false);
-                // disabled because it causes some devices to crash
-                surfaceViewRenderer.setEnableHardwareScaler(false);
-                surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
-                surfaceViewRenderer.setOnClickListener(videoOnClickListener);
-                remoteRenderersLayout.addView(relativeLayout);
-                if (hasExternalSignalingServer) {
-                    gotNick(session, webSocketClient.getDisplayNameForSession(session), type);
-                } else {
-                    gotNick(session, getPeerConnectionWrapperForSessionIdAndType(session, type, false).getNick(), type);
-                }
-
-                if ("video".equals(type)) {
-                    setupAvatarForSession(session);
-                }
-
-                callControls.setZ(100.0f);
-            });
-        }
-    }
-
-    private void gotNick(String sessionId, String nick, String type) {
-        String remoteRendererTag = sessionId + "+" + type;
-
-        if (controllerCallLayout != null) {
-            RelativeLayout relativeLayout = remoteRenderersLayout.findViewWithTag(remoteRendererTag);
-            TextView textView = relativeLayout.findViewById(R.id.peer_nick_text_view);
-            if (!textView.getText().equals(nick)) {
-                textView.setText(nick);
-
-                if (getActivity() != null && type.equals("video")) {
-                    getActivity().runOnUiThread(() -> setupAvatarForSession(sessionId));
-                }
-            }
-        }
-    }
-
     @OnClick(R.id.callStateRelativeLayoutView)
     public void onConnectingViewClick() {
         if (currentCallStatus.equals(CallStatus.CALLING_TIMEOUT)) {
@@ -2180,8 +2166,8 @@ public class CallController extends BaseController {
                             callStateView.setVisibility(View.VISIBLE);
                         }
 
-                        if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) {
-                            remoteRenderersLayout.setVisibility(View.INVISIBLE);
+                        if (gridView.getVisibility() != View.INVISIBLE) {
+                            gridView.setVisibility(View.INVISIBLE);
                         }
 
                         if (progressBar.getVisibility() != View.VISIBLE) {
@@ -2206,8 +2192,8 @@ public class CallController extends BaseController {
                             progressBar.setVisibility(View.GONE);
                         }
 
-                        if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) {
-                            remoteRenderersLayout.setVisibility(View.INVISIBLE);
+                        if (gridView.getVisibility() != View.INVISIBLE) {
+                            gridView.setVisibility(View.INVISIBLE);
                         }
 
                         errorImageView.setImageResource(R.drawable.ic_av_timer_timer_24dp);
@@ -2225,8 +2211,8 @@ public class CallController extends BaseController {
                         if (callStateView.getVisibility() != View.VISIBLE) {
                             callStateView.setVisibility(View.VISIBLE);
                         }
-                        if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) {
-                            remoteRenderersLayout.setVisibility(View.INVISIBLE);
+                        if (gridView.getVisibility() != View.INVISIBLE) {
+                            gridView.setVisibility(View.INVISIBLE);
                         }
                         if (progressBar.getVisibility() != View.VISIBLE) {
                             progressBar.setVisibility(View.VISIBLE);
@@ -2258,9 +2244,9 @@ public class CallController extends BaseController {
                             }
                         }
 
-                        if (remoteRenderersLayout != null) {
-                            if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) {
-                                remoteRenderersLayout.setVisibility(View.INVISIBLE);
+                        if (gridView != null) {
+                            if (gridView.getVisibility() != View.INVISIBLE) {
+                                gridView.setVisibility(View.INVISIBLE);
                             }
                         }
 
@@ -2296,9 +2282,9 @@ public class CallController extends BaseController {
                             }
                         }
 
-                        if (remoteRenderersLayout != null) {
-                            if (remoteRenderersLayout.getVisibility() != View.VISIBLE) {
-                                remoteRenderersLayout.setVisibility(View.VISIBLE);
+                        if (gridView != null) {
+                            if (gridView.getVisibility() != View.VISIBLE) {
+                                gridView.setVisibility(View.VISIBLE);
                             }
                         }
 
@@ -2322,9 +2308,9 @@ public class CallController extends BaseController {
                         }
 
 
-                        if (remoteRenderersLayout != null) {
-                            if (remoteRenderersLayout.getVisibility() != View.INVISIBLE) {
-                                remoteRenderersLayout.setVisibility(View.INVISIBLE);
+                        if (gridView != null) {
+                            if (gridView.getVisibility() != View.INVISIBLE) {
+                                gridView.setVisibility(View.INVISIBLE);
                             }
                         }
 
@@ -2349,7 +2335,7 @@ public class CallController extends BaseController {
                             callVoiceOrVideoTextView.setText(getDescriptionForCallType());
                             callStateTextView.setText(R.string.nc_leaving_call);
                             callStateView.setVisibility(View.VISIBLE);
-                            remoteRenderersLayout.setVisibility(View.INVISIBLE);
+                            gridView.setVisibility(View.INVISIBLE);
                             progressBar.setVisibility(View.VISIBLE);
                             errorImageView.setVisibility(View.GONE);
                         }
@@ -2362,7 +2348,7 @@ public class CallController extends BaseController {
 
     private String getDescriptionForCallType() {
         String appName = getResources().getString(R.string.nc_app_name);
-        if (isVoiceOnlyCall){
+        if (isVoiceOnlyCall) {
             return String.format(getResources().getString(R.string.nc_call_voice), appName);
         } else {
             return String.format(getResources().getString(R.string.nc_call_video), appName);
@@ -2374,18 +2360,20 @@ public class CallController extends BaseController {
         Uri ringtoneUri;
         if (isIncomingCallFromNotification) {
             ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() +
-                    "/raw/librem_by_feandesign_call");
+                                            "/raw/librem_by_feandesign_call");
         } else {
             ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/raw" +
-                    "/tr110_1_kap8_3_freiton1");
+                                            "/tr110_1_kap8_3_freiton1");
         }
         if (getActivity() != null) {
             mediaPlayer = new MediaPlayer();
             try {
                 mediaPlayer.setDataSource(Objects.requireNonNull(getActivity()), ringtoneUri);
                 mediaPlayer.setLooping(true);
-                AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(AudioAttributes
-                        .CONTENT_TYPE_SONIFICATION).setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
+                AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(
+                        AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                        .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                        .build();
                 mediaPlayer.setAudioAttributes(audioAttributes);
 
                 mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start());
@@ -2438,35 +2426,37 @@ public class CallController extends BaseController {
         }
     }
 
-    private class VideoClickListener implements View.OnClickListener {
-
-        @Override
-        public void onClick(View v) {
-            showCallControls();
-        }
-    }
-
     @Subscribe(threadMode = ThreadMode.BACKGROUND)
     public void onMessageEvent(NetworkEvent networkEvent) {
-        if (networkEvent.getNetworkConnectionEvent().equals(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED)) {
+        if (networkEvent.getNetworkConnectionEvent()
+                .equals(NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED)) {
             if (handler != null) {
                 handler.removeCallbacksAndMessages(null);
             }
-
-            /*if (!hasMCU) {
-                setCallState(CallStatus.RECONNECTING);
-                hangupNetworkCalls(false);
-            }*/
-
-        } else if (networkEvent.getNetworkConnectionEvent().equals(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED)) {
+        } else if (networkEvent.getNetworkConnectionEvent()
+                .equals(NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED)) {
             if (handler != null) {
                 handler.removeCallbacksAndMessages(null);
             }
+        }
+    }
+
+    private class SelfVideoTouchListener implements View.OnTouchListener {
 
-           /* if (!hasMCU) {
-                setCallState(CallStatus.OFFLINE);
-                hangup(false);
-            }*/
+        @SuppressLint("ClickableViewAccessibility")
+        @Override
+        public boolean onTouch(View view, MotionEvent event) {
+            long duration = event.getEventTime() - event.getDownTime();
+
+            if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
+                float newY = event.getRawY() - selfVideoView.getHeight() / (float) 2;
+                float newX = event.getRawX() - selfVideoView.getWidth() / (float) 2;
+                selfVideoView.setY(newY);
+                selfVideoView.setX(newX);
+            } else if (event.getActionMasked() == MotionEvent.ACTION_UP && duration < 100) {
+                switchCamera();
+            }
+            return true;
         }
     }
 }

+ 4 - 17
app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java

@@ -52,9 +52,7 @@ import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
-import android.view.WindowManager;
 import android.widget.EditText;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.annotation.ColorInt;
@@ -63,15 +61,12 @@ import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.XmlRes;
-import androidx.appcompat.app.AppCompatDelegate;
 import androidx.appcompat.widget.AppCompatDrawableManager;
 import androidx.appcompat.widget.SearchView;
 import androidx.core.content.ContextCompat;
-import androidx.core.content.res.ResourcesCompat;
 import androidx.core.graphics.ColorUtils;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.emoji.text.EmojiCompat;
-import androidx.viewpager.widget.ViewPager;
 
 import com.facebook.common.executors.UiThreadImmediateExecutorService;
 import com.facebook.common.references.CloseableReference;
@@ -95,7 +90,6 @@ import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.events.UserMentionClickEvent;
 import com.nextcloud.talk.models.database.UserEntity;
-import com.nextcloud.talk.utils.preferences.AppPreferences;
 import com.nextcloud.talk.utils.text.Spans;
 
 import org.greenrobot.eventbus.EventBus;
@@ -108,17 +102,6 @@ import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import androidx.annotation.ColorInt;
-import androidx.annotation.ColorRes;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.XmlRes;
-import androidx.appcompat.widget.AppCompatDrawableManager;
-import androidx.core.content.ContextCompat;
-import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.emoji.text.EmojiCompat;
-
 public class DisplayUtils {
 
     private static final String TAG = "DisplayUtils";
@@ -239,6 +222,10 @@ public class DisplayUtils {
                 context.getResources().getDisplayMetrics()) + 0.5f);
     }
 
+    public static float convertPixelToDp(float px, Context context) {
+        return px / context.getResources().getDisplayMetrics().density;
+    }
+
     // Solution inspired by https://stackoverflow.com/questions/34936590/why-isnt-my-vector-drawable-scaling-as-expected
     public static void useCompatVectorIfNeeded() {
         if (Build.VERSION.SDK_INT < 23) {

+ 1 - 0
app/src/main/java/com/nextcloud/talk/webrtc/MagicPeerConnectionWrapper.java

@@ -50,6 +50,7 @@ import java.util.List;
 @AutoInjector(NextcloudTalkApplication.class)
 public class MagicPeerConnectionWrapper {
     private static String TAG = "MagicPeerConnectionWrapper";
+
     private List<IceCandidate> iceCandidates = new ArrayList<>();
     private PeerConnection peerConnection;
     private String sessionId;

+ 25 - 37
app/src/main/res/layout/call_item.xml

@@ -2,7 +2,9 @@
   ~ Nextcloud Talk application
   ~
   ~ @author Mario Danic
+  ~ @author Marcel Hibbe
   ~ @author Andy Scherzinger
+  ~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
   ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
   ~ Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
   ~
@@ -25,43 +27,8 @@
     android:id="@+id/relative_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_weight="1"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/peer_nick_text_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentStart="true"
-        android:layout_alignParentTop="true"
-        android:layout_marginStart="8dp"
-        android:layout_marginEnd="8dp"
-        android:layout_marginBottom="8dp"
-        android:textColor="@android:color/white"
-        android:layout_centerHorizontal="true"/>
-
-    <ImageView
-        android:id="@+id/remote_audio_off"
-        android:layout_width="16dp"
-        android:layout_height="16dp"
-        android:layout_below="@id/peer_nick_text_view"
-        android:layout_alignParentStart="true"
-        android:layout_marginStart="8dp"
-        android:layout_marginEnd="8dp"
-        android:contentDescription="@string/nc_remote_audio_off"
-        android:src="@drawable/ic_mic_off_white_24px"
-        android:visibility="invisible" />
-
-    <ImageView
-        android:id="@+id/remote_video_off"
-        android:layout_width="16dp"
-        android:layout_height="16dp"
-        android:layout_below="@id/peer_nick_text_view"
-        android:layout_marginStart="8dp"
-        android:layout_toEndOf="@id/remote_audio_off"
-        android:contentDescription="@string/nc_remote_video_off"
-        android:src="@drawable/ic_videocam_off_white_24px"
-        android:visibility="invisible" />
+    android:orientation="vertical"
+    android:gravity="center">
 
     <com.facebook.drawee.view.SimpleDraweeView
         android:id="@+id/avatarImageView"
@@ -76,4 +43,25 @@
         android:layout_height="match_parent"
         android:visibility="invisible" />
 
+    <TextView
+        android:id="@+id/peer_nick_text_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="6dp"
+        android:layout_marginStart="10dp"
+        android:textColor="@android:color/white" />
+
+    <ImageView
+        android:id="@+id/remote_audio_off"
+        android:layout_width="16dp"
+        android:layout_height="16dp"
+        android:layout_marginBottom="6dp"
+        android:layout_marginStart="10dp"
+        android:layout_alignParentBottom="true"
+        android:layout_toEndOf="@id/peer_nick_text_view"
+        android:src="@drawable/ic_mic_off_white_24px"
+        android:contentDescription="@string/nc_remote_audio_off"
+        android:visibility="invisible" />
+
 </RelativeLayout>

+ 34 - 32
app/src/main/res/layout/controller_call.xml

@@ -45,13 +45,39 @@
             android:layout_weight="1"
             tools:visibility="visible">
 
-            <LinearLayout
-                android:id="@+id/remote_renderers_layout"
+            <GridView
+                android:id="@+id/gridview"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:animateLayoutChanges="true"
-                android:orientation="vertical">
-            </LinearLayout>
+                android:gravity="center"
+                android:stretchMode="columnWidth"
+                android:numColumns="2"
+                />
+
+            <FrameLayout
+                android:id="@+id/selfVideoView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+
+                <org.webrtc.SurfaceViewRenderer
+                    android:id="@+id/pip_video_view"
+                    android:layout_width="@dimen/large_preview_dimension"
+                    android:layout_height="150dp"
+                    android:layout_gravity="center"
+                    android:layout_margin="16dp"
+                    android:visibility="invisible"
+                    android:clickable="false"
+                    tools:visibility="visible" />
+
+                <com.facebook.drawee.view.SimpleDraweeView
+                    android:id="@+id/call_control_switch_camera"
+                    android:layout_width="40dp"
+                    android:layout_height="40dp"
+                    android:layout_gravity="center_horizontal|bottom"
+                    android:layout_marginBottom="20dp"
+                    app:placeholderImage="@drawable/ic_switch_video_white_24px"
+                    app:roundAsCircle="true" />
+            </FrameLayout>
 
             <LinearLayout
                 android:id="@+id/callInfosLinearLayout"
@@ -83,35 +109,11 @@
                     android:ellipsize="marquee"
                     android:textAlignment="center"
                     android:textColor="@color/white"
-                    android:textSize="28sp"
+                    android:textSize="22sp"
+                    android:textStyle="bold"
                     tools:text="Marsellus Wallace" />
             </LinearLayout>
 
-            <FrameLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentEnd="true"
-                android:layout_below="@id/callInfosLinearLayout">
-
-                <org.webrtc.SurfaceViewRenderer
-                    android:id="@+id/pip_video_view"
-                    android:layout_width="@dimen/large_preview_dimension"
-                    android:layout_height="150dp"
-                    android:layout_gravity="center"
-                    android:layout_margin="16dp"
-                    android:visibility="invisible"
-                    tools:visibility="visible"/>
-
-                <com.facebook.drawee.view.SimpleDraweeView
-                    android:id="@+id/call_control_switch_camera"
-                    android:layout_width="40dp"
-                    android:layout_height="40dp"
-                    android:layout_gravity="center_horizontal|bottom"
-                    android:layout_marginBottom="20dp"
-                    app:placeholderImage="@drawable/ic_switch_video_white_24px"
-                    app:roundAsCircle="true" />
-            </FrameLayout>
-
             <View android:id="@+id/verticalCenter"
                 android:layout_width="0dp"
                 android:layout_height="0dp"
@@ -137,7 +139,7 @@
         android:background="@android:color/transparent"
         android:gravity="center"
         android:layout_alignBottom="@id/linearWrapperLayout"
-        android:layout_marginBottom="10dp">
+        android:layout_marginBottom="16dp">
 
         <com.facebook.drawee.view.SimpleDraweeView
             android:id="@+id/callControlToggleChat"

+ 2 - 1
app/src/main/res/layout/controller_call_notification.xml

@@ -101,7 +101,8 @@
             android:ellipsize="marquee"
             android:textAlignment="center"
             android:textColor="@color/white"
-            android:textSize="28sp"
+            android:textSize="22sp"
+            android:textStyle="bold"
             tools:text="Victor Gregorius Magnus" />
 
         <TextView

+ 0 - 1
app/src/main/res/values/strings.xml

@@ -285,7 +285,6 @@
     <string name="nc_formatted_message_you">You: %1$s</string>
     <string name="nc_message_read">Message read</string>
     <string name="nc_message_sent">Message sent</string>
-    <string name="nc_remote_video_off">Remote video off</string>
     <string name="nc_remote_audio_off">Remote audio off</string>
     <string name="nc_add_attachment">Add attachment</string>