diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java index 0307b89ae11848c92b322664bb6b67d118fcd3ba..050fb051189d1d4dfc784862e938be552749dd09 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java @@ -1,3 +1,22 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program 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. + * + * This program 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ package cx.ring.adapters; import android.content.Context; @@ -11,16 +30,18 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; +import cx.ring.fragments.CallFragment; import cx.ring.views.AvatarFactory; import cx.ring.databinding.ItemConferenceParticipantBinding; -import cx.ring.fragments.CallFragment; + +import net.jami.model.Conference; import net.jami.model.Contact; import net.jami.model.Call; import cx.ring.views.ParticipantView; public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView> { protected final ConfParticipantAdapter.ConfParticipantSelected onSelectedCallback; - private List<Call> calls = null; + private List<Conference.ParticipantInfo> calls = null; public ConfParticipantAdapter(@NonNull ConfParticipantSelected cb) { onSelectedCallback = cb; @@ -34,22 +55,23 @@ public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView @Override public void onBindViewHolder(@NonNull ParticipantView holder, int position) { - final Call call = calls.get(position); - final Contact contact = call.getContact(); + final Conference.ParticipantInfo info = calls.get(position); + final Contact contact = info.contact; + final Context context = holder.itemView.getContext(); - Call.CallStatus status = call.getCallStatus(); - if (status == Call.CallStatus.CURRENT) { + if (info.call != null && info.call.getCallStatus() != Call.CallStatus.CURRENT) { + holder.binding.displayName.setText(String.format("%s\n%s", contact.getDisplayName(), context.getText(CallFragment.callStateToHumanState(info.call.getCallStatus())))); + holder.binding.photo.setAlpha(.5f); + } else { holder.binding.displayName.setText(contact.getDisplayName()); holder.binding.photo.setAlpha(1f); - } else { - holder.binding.displayName.setText(String.format("%s\n%s", contact.getDisplayName(), context.getText(CallFragment.callStateToHumanState(status)))); - holder.binding.photo.setAlpha(.5f); } + if (holder.disposable != null) holder.disposable.dispose(); holder.disposable = AvatarFactory.getAvatar(context, contact) .subscribe(holder.binding.photo::setImageDrawable); - holder.itemView.setOnClickListener(view -> onSelectedCallback.onParticipantSelected(view, call)); + holder.itemView.setOnClickListener(view -> onSelectedCallback.onParticipantSelected(view, info)); } @Override @@ -57,8 +79,8 @@ public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView return calls == null ? 0 : calls.size(); } - public void updateFromCalls(@NonNull final List<Call> contacts) { - final List<Call> oldCalls = calls; + public void updateFromCalls(@NonNull final List<Conference.ParticipantInfo> contacts) { + final List<Conference.ParticipantInfo> oldCalls = calls; calls = contacts; if (oldCalls != null) { DiffUtil.calculateDiff(new DiffUtil.Callback() { @@ -88,6 +110,6 @@ public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView } public interface ConfParticipantSelected { - void onParticipantSelected(View view, Call contact); + void onParticipantSelected(View view, Conference.ParticipantInfo contact); } } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java index 687e76f29aa4f90b6810a1d9017e681bac39af85..05774ee7abe17c7383612ec6bd677d44fcd62105 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java @@ -96,7 +96,6 @@ import net.jami.model.Uri; import net.jami.services.DeviceRuntimeService; import net.jami.services.HardwareService; import net.jami.services.NotificationService; -import net.jami.utils.StringUtils; import java.util.ArrayList; import java.util.Iterator; @@ -138,7 +137,6 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements public static final String ACTION_GET_CALL = "GET_CALL"; public static final String KEY_ACTION = "action"; - public static final String KEY_ACCOUNT_ID = "accountId"; public static final String KEY_CONF_ID = "confId"; public static final String KEY_AUDIO_ONLY = "AUDIO_ONLY"; @@ -344,7 +342,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements return binding.getRoot(); } - private TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() { + private final TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mPreviewSurfaceWidth = width; @@ -380,7 +378,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements binding.pluginPreviewHandle.setAlpha(hiddenState); } - @SuppressLint("ClickableViewAccessibility") + @SuppressLint({"ClickableViewAccessibility", "RtlHardcoded", "WakelockTimeout"}) @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { setHasOptionsMenu(true); @@ -741,6 +739,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements * * @param isAudioOnly true if it is an audio call */ + @SuppressLint("WakelockTimeout") @Override public void handleCallWakelock(boolean isAudioOnly) { if (isAudioOnly) { @@ -841,15 +840,13 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { super.onOptionsItemSelected(item); - switch (item.getItemId()) { - case android.R.id.home: - presenter.chatClick(); - break; - case R.id.menuitem_dialpad: - presenter.dialpadClick(); - break; - case R.id.menuitem_video_plugins: - displayVideoPluginsCarousel(); + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + presenter.chatClick(); + } else if (itemId == R.id.menuitem_dialpad) { + presenter.dialpadClick(); + } else if (itemId == R.id.menuitem_video_plugins) { + displayVideoPluginsCarousel(); } return true; } @@ -949,9 +946,8 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements public void updateContactBubble(@NonNull final List<Call> contacts) { Log.w(TAG, "updateContactBubble " + contacts.size()); - mConferenceMode = contacts.size() > 1; - String username = mConferenceMode ? "Conference with " + contacts.size() + " people" : contacts.get(0).getContact().getRingUsername(); - String displayName = mConferenceMode ? null : contacts.get(0).getContact().getDisplayName(); + String username = contacts.size() > 1 ? "Conference with " + contacts.size() + " people" : contacts.get(0).getContact().getDisplayName(); + String displayName = contacts.size() > 1 ? null : contacts.get(0).getContact().getDisplayName(); boolean hasProfileName = displayName != null && !displayName.contentEquals(username); @@ -987,15 +983,42 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements .build(getActivity()) ); - if (!mConferenceMode) { + } + + @Override + public void updateConfInfo(List<Conference.ParticipantInfo> participantInfo) { + Log.w(TAG, "updateConfInfo " + participantInfo); + + mConferenceMode = participantInfo.size() > 1; + + binding.participantLabelContainer.removeAllViews(); + if (!participantInfo.isEmpty()) { + LayoutInflater inflater = LayoutInflater.from(binding.participantLabelContainer.getContext()); + for (Conference.ParticipantInfo i : participantInfo) { + String displayName = i.contact.getDisplayName(); + if (!TextUtils.isEmpty(displayName)) { + ItemParticipantLabelBinding label = ItemParticipantLabelBinding.inflate(inflater); + PercentFrameLayout.LayoutParams params = new PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.getPercentLayoutInfo().leftMarginPercent = i.x / (float) mVideoWidth; + params.getPercentLayoutInfo().topMarginPercent = i.y / (float) mVideoHeight; + label.participantName.setText(displayName); + label.moderator.setVisibility(i.isModerator ? View.VISIBLE : View.GONE); + label.mute.setVisibility(i.audioMuted ? View.VISIBLE : View.GONE); + binding.participantLabelContainer.addView(label.getRoot(), params); + } + } + } + binding.participantLabelContainer.setVisibility(participantInfo.isEmpty() ? View.GONE : View.VISIBLE); + + if (participantInfo.isEmpty() || participantInfo.size() < 2) { binding.confControlGroup.setVisibility(View.GONE); } else { binding.confControlGroup.setVisibility(View.VISIBLE); if (confAdapter == null) { - confAdapter = new ConfParticipantAdapter((view, call) -> { + confAdapter = new ConfParticipantAdapter((view, info) -> { if (presenter == null) return; - boolean maximized = presenter.isMaximized(call); + boolean maximized = presenter.isMaximized(info); PopupMenu popup = new PopupMenu(view.getContext(), view); popup.inflate(R.menu.conference_participant_actions); MenuBuilder menu = (MenuBuilder) popup.getMenu(); @@ -1008,7 +1031,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements maxItem.setTitle(R.string.action_call_maximize); maxItem.setIcon(R.drawable.baseline_open_in_full_24); } - if (!call.isAudioMuted()) { + if (!info.audioMuted) { muteItem.setTitle(R.string.action_call_mute); muteItem.setIcon(R.drawable.baseline_mic_off_24); } else { @@ -1018,27 +1041,18 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements popup.setOnMenuItemClickListener(item -> { if (presenter == null) return false; - switch (item.getItemId()) { - case R.id.conv_contact_details: - presenter.openParticipantContact(call); - break; - case R.id.conv_contact_hangup: - presenter.hangupParticipant(call); - break; - case R.id.conv_mute: - if (!call.isAudioMuted()) { - call.muteAudio(true); - presenter.muteParticipant(call, true); - } else { - call.muteAudio(false); - presenter.muteParticipant(call, false); - } - break; - case R.id.conv_contact_maximize: - presenter.maximizeParticipant(call); - break; - default: - return false; + int itemId = item.getItemId(); + if (itemId == R.id.conv_contact_details) { + presenter.openParticipantContact(info); + } else if (itemId == R.id.conv_contact_hangup) { + presenter.hangupParticipant(info); + } else if (itemId == R.id.conv_mute) { + //call.muteAudio(!info.audioMuted); + presenter.muteParticipant(info, !info.audioMuted); + } else if (itemId == R.id.conv_contact_maximize) { + presenter.maximizeParticipant(info); + } else { + return false; } return true; }); @@ -1047,34 +1061,12 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements menuHelper.show(); }); } - confAdapter.updateFromCalls(contacts); + confAdapter.updateFromCalls(participantInfo); if (binding.confControlGroup.getAdapter() == null) binding.confControlGroup.setAdapter(confAdapter); } } - @Override - public void updateConfInfo(List<Conference.ParticipantInfo> info) { - binding.participantLabelContainer.removeAllViews(); - if (!info.isEmpty()) { - LayoutInflater inflater = LayoutInflater.from(binding.participantLabelContainer.getContext()); - for (Conference.ParticipantInfo i : info) { - String displayName = i.contact.getDisplayName(); - if (!TextUtils.isEmpty(displayName)) { - ItemParticipantLabelBinding label = ItemParticipantLabelBinding.inflate(inflater); - PercentFrameLayout.LayoutParams params = new PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.getPercentLayoutInfo().leftMarginPercent = i.x / (float) mVideoWidth; - params.getPercentLayoutInfo().topMarginPercent = i.y / (float) mVideoHeight; - label.participantName.setText(displayName); - label.moderator.setVisibility(i.isModerator ? View.VISIBLE : View.GONE); - label.mute.setVisibility(i.audioMuted ? View.VISIBLE : View.GONE); - binding.participantLabelContainer.addView(label.getRoot(), params); - } - } - } - binding.participantLabelContainer.setVisibility(info.isEmpty() ? View.GONE : View.VISIBLE); - } - @Override public void updateParticipantRecording(Set<Contact> contacts) { if (contacts.size() == 0) { @@ -1131,7 +1123,9 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements requireActivity().invalidateOptionsMenu(); CallActivity callActivity = (CallActivity) getActivity(); - callActivity.showSystemUI(); + if (callActivity != null) { + callActivity.showSystemUI(); + } } @Override @@ -1471,6 +1465,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements /** * Function that is called to show/hide the plugins recycler viewer and update UI */ + @SuppressLint("UseCompatLoadingForDrawables") public void displayVideoPluginsCarousel() { choosePluginMode = !choosePluginMode; @@ -1488,7 +1483,7 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements for (String callMediaHandler : callMediaHandlers) { Map<String, String> details = getCallMediaHandlerDetails(callMediaHandler); String drawablePath = details.get("iconPath"); - if (drawablePath.endsWith("svg")) + if (drawablePath != null && drawablePath.endsWith("svg")) drawablePath = drawablePath.replace(".svg", ".png"); Drawable handlerIcon = Drawable.createFromPath(drawablePath); if (handlerIcon == null) { @@ -1513,17 +1508,15 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements if(callMediaHandlers.size() > 0) { // If no previous plugin was active, take the first, else previous int position; - if(previousPluginPosition < 1) { + if (previousPluginPosition < 1) { rp.scrollToPosition(1); position = 1; previousPluginPosition = 1; } else { position = previousPluginPosition; } - if (position > 0) { - String callMediaId = callMediaHandlers.get(position-1); - presenter.startPlugin(callMediaId); - } + String callMediaId = callMediaHandlers.get(position-1); + presenter.startPlugin(callMediaId); } } else { @@ -1546,12 +1539,11 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements /** * Called whenever a plugin drawable in the recycler picker is clicked or scrolled to - * @param position */ @Override public void onItemSelected(int position) { Log.i(TAG, "selected position: " + position); - /** If there was a different plugin before, unload it + /* If there was a different plugin before, unload it * If previousPluginPosition = -1 or 0, there was no plugin */ if (previousPluginPosition > 0) { @@ -1569,13 +1561,12 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements /** * Called whenever a plugin drawable in the recycler picker is clicked - * @param position */ @Override public void onItemClicked(int position) { Log.i(TAG, "selected position: " + position); if (position == 0) { - /** If there was a different plugin before, unload it + /* If there was a different plugin before, unload it * If previousPluginPosition = -1 or 0, there was no plugin */ if (previousPluginPosition > 0) { @@ -1585,7 +1576,9 @@ public class CallFragment extends BaseSupportFragment<CallPresenter> implements } CallActivity callActivity = (CallActivity) getActivity(); - callActivity.showSystemUI(); + if (callActivity != null) { + callActivity.showSystemUI(); + } toggleVideoPluginsCarousel(false); displayVideoPluginsCarousel(); diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java index 0d140715d382ab533a000aee041a5a8bedc78410..6c411718f0f7b8eb6f8b99af0839201451c663fd 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java @@ -436,7 +436,7 @@ public class TVCallFragment extends BaseSupportFragment<CallPresenter> implement .build(getActivity()) ); - if (!mConferenceMode) { + /*if (!mConferenceMode) { binding.confControlGroup.setVisibility(View.GONE); } else { binding.confControlGroup.setVisibility(View.VISIBLE); @@ -464,7 +464,7 @@ public class TVCallFragment extends BaseSupportFragment<CallPresenter> implement confAdapter.updateFromCalls(calls); if (binding.confControlGroup.getAdapter() == null) binding.confControlGroup.setAdapter(confAdapter); - } + }*/ } @Override @@ -686,6 +686,36 @@ public class TVCallFragment extends BaseSupportFragment<CallPresenter> implement } } binding.participantLabelContainer.setVisibility(info.isEmpty() ? View.GONE : View.VISIBLE); + + if (!mConferenceMode) { + binding.confControlGroup.setVisibility(View.GONE); + } else { + binding.confControlGroup.setVisibility(View.VISIBLE); + if (confAdapter == null) { + confAdapter = new ConfParticipantAdapter((view, call) -> { + Context context = requireContext(); + PopupMenu popup = new PopupMenu(context, view); + popup.inflate(R.menu.conference_participant_actions); + popup.setOnMenuItemClickListener(item -> { + int itemId = item.getItemId(); + if (itemId == R.id.conv_contact_details) { + presenter.openParticipantContact(call); + } else if (itemId == R.id.conv_contact_hangup) { + presenter.hangupParticipant(call); + } else { + return false; + } + return true; + }); + MenuPopupHelper menuHelper = new MenuPopupHelper(context, (MenuBuilder) popup.getMenu(), view); + menuHelper.setForceShowIcon(true); + menuHelper.show(); + }); + } + confAdapter.updateFromCalls(info); + if (binding.confControlGroup.getAdapter() == null) + binding.confControlGroup.setAdapter(confAdapter); + } } @Override diff --git a/ring-android/app/src/main/res/drawable/baseline_moderateur.xml b/ring-android/app/src/main/res/drawable/baseline_moderateur.xml index 1361cac0d75e7243c3faf464a7656f47cbe4a059..34126174fa73e00fe217781749e21a2ceef8af81 100644 --- a/ring-android/app/src/main/res/drawable/baseline_moderateur.xml +++ b/ring-android/app/src/main/res/drawable/baseline_moderateur.xml @@ -2,11 +2,11 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24" - android:viewportHeight="24"> - <path + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> +<path android:pathData="M12.0856,6.1741L15.2189,10.3993L21.4127,6.2318L21.4127,18.3908L2.7376,18.3908L2.7376,6.2318L8.9295,10.3981L12.0856,6.1741Z" android:strokeWidth="1.3" - android:fillColor="#00000000" - android:fillType="nonZero" - android:strokeColor="#000000"/> + android:fillColor="@android:color/white" + android:strokeColor="@android:color/white"/> </vector> diff --git a/ring-android/app/src/main/res/layout/item_participant_label.xml b/ring-android/app/src/main/res/layout/item_participant_label.xml index 41039d9a0005a7b8b918ef2df4f18c54fb0b5068..1b206b01d80df2e4b173724aaa62758e45675faf 100644 --- a/ring-android/app/src/main/res/layout/item_participant_label.xml +++ b/ring-android/app/src/main/res/layout/item_participant_label.xml @@ -1,41 +1,47 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - android:orientation="horizontal" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:background="@drawable/background_conference_label" - android:gravity="center_vertical" - android:padding="12dp"> + android:layout_height="wrap_content"> - <TextView - android:id="@+id/participant_name" + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - tools:text="Georges Longnom de Chateaubrillant" - android:textColor="@color/white" - android:ellipsize="marquee" - android:singleLine="true" /> + android:layout_margin="4dp" + android:background="@drawable/background_conference_label" + android:gravity="center_vertical" + android:orientation="horizontal" + android:padding="12dp"> - <ImageView - android:id="@+id/moderator" - android:layout_width="16dp" - android:layout_height="16dp" - android:src="@drawable/baseline_moderateur" - android:tint="@color/white" - android:layout_marginLeft="4dp" - android:visibility="gone" - tools:visibility="visible" - /> + <TextView + android:id="@+id/participant_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:ellipsize="marquee" + android:singleLine="true" + android:textColor="@color/white" + tools:text="Georges Longname of Chateaubrillant" /> - <ImageView - android:id="@+id/mute" - android:layout_width="16dp" - android:layout_height="16dp" - android:layout_marginLeft="4dp" - android:src="@drawable/baseline_mic_off_24" - android:visibility="gone" - tools:visibility="visible" - /> + <ImageView + android:id="@+id/moderator" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_margin="4dp" + android:contentDescription="@string/call_moderator" + android:src="@drawable/baseline_moderateur" + android:visibility="gone" + tools:visibility="visible" /> -</LinearLayout> \ No newline at end of file + <ImageView + android:id="@+id/mute" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_margin="4dp" + android:contentDescription="@string/call_muted" + android:src="@drawable/baseline_mic_off_24" + android:visibility="gone" + tools:visibility="visible" /> + + </LinearLayout> +</FrameLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/values/strings_call.xml b/ring-android/app/src/main/res/values/strings_call.xml index e865eb38829e8b9b639b4aae88d637f7680c4231..1906f3b40d3a3b4d88362ac650c10da2b2227fb9 100644 --- a/ring-android/app/src/main/res/values/strings_call.xml +++ b/ring-android/app/src/main/res/values/strings_call.xml @@ -38,4 +38,6 @@ along with this program; if not, write to the Free Software <string name="call_human_state_over">Over</string> <string name="call_human_state_none" translatable="false" /> <!-- Conferences --> + <string name="call_moderator">Moderator</string> + <string name="call_muted">Muted</string> </resources> \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java b/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java index 6120ebd47804c9668b1ec240b6d665d071f3662b..a11fe3eac0074fb85be835ebf81ff2eb3581dbe8 100644 --- a/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java +++ b/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java @@ -24,6 +24,7 @@ import net.jami.daemon.JamiService; import net.jami.facades.ConversationFacade; import net.jami.model.Call; import net.jami.model.Conference; +import net.jami.model.Contact; import net.jami.model.Conversation; import net.jami.model.ConversationHistory; import net.jami.model.Uri; @@ -401,7 +402,8 @@ public class CallPresenter extends RootPresenter<CallView> { public void uiVisibilityChanged(boolean displayed) { - net.jami.call.CallView view = getView(); + Log.w(TAG, "uiVisibilityChanged " + mOnGoingCall + " " + displayed); + CallView view = getView(); if (view != null) view.displayHangupButton(mOnGoingCall && displayed); } @@ -469,7 +471,7 @@ public class CallPresenter extends RootPresenter<CallView> { } private void confUpdate(Conference call) { - Log.w(TAG, "confUpdate " + call.getId()); + Log.w(TAG, "confUpdate " + call.getId() + " " + call.getState()); Call.CallStatus status = call.getState(); if (status == Call.CallStatus.HOLD) { @@ -522,12 +524,13 @@ public class CallPresenter extends RootPresenter<CallView> { } } - public void maximizeParticipant(Call call) { - if (mConference.getMaximizedCall() == call) - call = null; - mConference.setMaximizedCall(call); - if (call != null) { - mCallService.setConfMaximizedParticipant(mConference.getConfId(), call.getDaemonIdString()); + public void maximizeParticipant(Conference.ParticipantInfo info) { + Contact contact = info == null ? null : info.contact; + if (mConference.getMaximizedParticipant() == contact) + info = null; + mConference.setMaximizedParticipant(contact); + if (info != null) { + mCallService.setConfMaximizedParticipant(mConference.getConfId(), info.contact.getUri()); } else { mCallService.setConfGridLayout(mConference.getConfId()); } @@ -648,6 +651,7 @@ public class CallPresenter extends RootPresenter<CallView> { if (confs.isEmpty()) { final Observer<Call> pendingObserver = new Observer<Call>() { private Call call = null; + @Override public void onSubscribe(@NonNull Disposable d) {} @@ -712,16 +716,18 @@ public class CallPresenter extends RootPresenter<CallView> { getView().startAddParticipant(mConference.getId()); } - public void hangupParticipant(Call call) { - mCallService.hangUp(call.getDaemonIdString()); + public void hangupParticipant(Conference.ParticipantInfo info) { + //mCallService. + if (info.call != null) + mCallService.hangUp(info.call.getDaemonIdString()); } - public void muteParticipant(Call call, boolean mute) { - mCallService.muteParticipant(call.getConfId(), call.getContact().getPrimaryNumber(), mute); + public void muteParticipant(Conference.ParticipantInfo info, boolean mute) { + mCallService.muteParticipant(mConference.getId(), info.contact.getPrimaryNumber(), mute); } - public void openParticipantContact(Call call) { - getView().goToContact(call.getAccount(), call.getContact()); + public void openParticipantContact(Conference.ParticipantInfo info) { + getView().goToContact(mConference.getFirstCall().getAccount(), info.contact); } public void stopCapture() { @@ -736,8 +742,8 @@ public class CallPresenter extends RootPresenter<CallView> { mHardwareService.stopScreenShare(); } - public boolean isMaximized(Call call) { - return mConference.getMaximizedCall() == call; + public boolean isMaximized(Conference.ParticipantInfo info) { + return mConference.getMaximizedParticipant() == info.contact; } public void startPlugin(String mediaHandlerId) { diff --git a/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java index 99bea15d3c55de4778da7c94d3806995cf52d2a1..01aef0be3c8d08727c9f7db742f38feb069bc76f 100644 --- a/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java +++ b/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java @@ -614,7 +614,7 @@ public class ConversationFacade { return; Contact contact = call.getContact(); String conversationId = call.getConversationId(); - Log.w(TAG, "CallStateChange " + call.getId() + " conversationId:" + conversationId); + Log.w(TAG, "CallStateChange " + call.getDaemonIdString() + " conversationId:" + conversationId); Conversation conversation = conversationId == null ? (contact == null ? null : account.getByUri(contact.getUri())) diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Conference.java b/ring-android/libringclient/src/main/java/net/jami/model/Conference.java index dbebde11199601331ceba8c2b65e2ec4760eb37a..cab4c89649d924853598652dfe275a99852e76f1 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Conference.java +++ b/ring-android/libringclient/src/main/java/net/jami/model/Conference.java @@ -35,11 +35,13 @@ import io.reactivex.subjects.Subject; public class Conference { public static class ParticipantInfo { - public Contact contact; + public final Call call; + public final Contact contact; public int x, y, w, h; public boolean videoMuted, audioMuted, isModerator; - public ParticipantInfo(Contact c, Map<String, String> i) { + public ParticipantInfo(Call call, Contact c, Map<String, String> i) { + this.call = call; contact = c; x = Integer.parseInt(i.get("x")); y = Integer.parseInt(i.get("y")); @@ -49,6 +51,10 @@ public class Conference { audioMuted = Boolean.parseBoolean(i.get("audioMuted")); isModerator = Boolean.parseBoolean(i.get("isModerator")); } + + public boolean isEmpty() { + return x == 0 && y == 0 && w == 0 && h == 0; + } } private final Subject<List<ParticipantInfo>> mParticipantInfo = BehaviorSubject.createDefault(Collections.emptyList()); @@ -59,7 +65,8 @@ public class Conference { private Call.CallStatus mConfState; private final ArrayList<Call> mParticipants; private boolean mRecording; - private Call mMaximizedCall; + private Contact mMaximizedParticipant; + private boolean isModerator; public Conference(Call call) { this(call.getDaemonIdString()); @@ -104,12 +111,12 @@ public class Conference { return mId; } - public void setMaximizedCall(Call call) { - mMaximizedCall = call; + public void setMaximizedParticipant(Contact contact) { + mMaximizedParticipant = contact; } - public Call getMaximizedCall() { - return mMaximizedCall; + public Contact getMaximizedParticipant() { + return mMaximizedParticipant; } public String getPluginId() { @@ -120,6 +127,14 @@ public class Conference { return mId; } + public void setIsModerator(boolean isModerator) { + this.isModerator = isModerator; + } + + public boolean getIsModerator() { + return isModerator; + } + public Call.CallStatus getState() { if (isSimpleCall()) { return mParticipants.get(0).getCallStatus(); diff --git a/ring-android/libringclient/src/main/java/net/jami/services/CallService.java b/ring-android/libringclient/src/main/java/net/jami/services/CallService.java index 7a6d4e64cc735904e9896dc165a794f76c16a4a1..d5904ea3b7b002b051a7929b8db964bf10a796d3 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/CallService.java +++ b/ring-android/libringclient/src/main/java/net/jami/services/CallService.java @@ -124,30 +124,53 @@ public class CallService { } public void onConferenceInfoUpdated(String confId, List<Map<String, String>> info) { + Log.w(TAG, "onConferenceInfoUpdated " + confId + " " + info); Conference conference = getConference(confId); + boolean isModerator = false; if (conference != null) { List<Conference.ParticipantInfo> newInfo = new ArrayList<>(info.size()); if (conference.isConference()) { for (Map<String, String> i : info) { Call call = conference.findCallByContact(Uri.fromString(i.get("uri"))); if (call != null) { - newInfo.add(new Conference.ParticipantInfo(call.getContact(), i)); + Conference.ParticipantInfo confInfo = new Conference.ParticipantInfo(call, call.getContact(), i); + if (confInfo.isEmpty()) { + Log.w(TAG, "onConferenceInfoUpdated: ignoring empty entry " + i); + continue; + } + if (confInfo.contact.isUser() && confInfo.isModerator) { + isModerator = true; + } + newInfo.add(confInfo); } else { + Log.w(TAG, "onConferenceInfoUpdated " + confId + " can't find call for " + i); // TODO } } } else { Account account = mAccountService.getAccount(conference.getCall().getAccount()); - for (Map<String, String> i : info) - newInfo.add(new Conference.ParticipantInfo(account.getContactFromCache(Uri.fromString(i.get("uri"))), i)); + for (Map<String, String> i : info) { + Conference.ParticipantInfo confInfo = new Conference.ParticipantInfo(null, account.getContactFromCache(Uri.fromString(i.get("uri"))), i); + if (confInfo.isEmpty()) { + Log.w(TAG, "onConferenceInfoUpdated: ignoring empty entry " + i); + continue; + } + if (confInfo.contact.isUser() && confInfo.isModerator) { + isModerator = true; + } + newInfo.add(confInfo); + } } + conference.setIsModerator(isModerator); conference.setInfo(newInfo); + } else { + Log.w(TAG, "onConferenceInfoUpdated can't find conference" + confId); } } - public void setConfMaximizedParticipant(String confId, String callId) { + public void setConfMaximizedParticipant(String confId, Uri uri) { mExecutor.execute(() -> { - JamiService.setActiveParticipant(confId, callId); + JamiService.setActiveParticipant(confId, uri == null ? "" : uri.getRawRingId()); JamiService.setConferenceLayout(confId, 1); }); } @@ -246,7 +269,7 @@ public class CallService { JamiService.muteLocalMedia(callId, "MEDIA_TYPE_VIDEO", true); } Call call = addCall(account, callId, number, Call.Direction.OUTGOING); - if (conversationUri.isSwarm()) + if (conversationUri != null && conversationUri.isSwarm()) call.setSwarmInfo(conversationUri.getRawRingId()); call.muteVideo(audioOnly); updateConnectionCount(); @@ -446,14 +469,14 @@ public class CallService { return currentCalls.get(callId); } - public Call getCurrentCallForContactId(String contactId) { + /*public Call getCurrentCallForContactId(String contactId) { for (Call call : currentCalls.values()) { if (contactId.contains(call.getContact().getPrimaryNumber())) { return call; } } return null; - } + }*/ public void removeCallForId(String callId) { synchronized (currentCalls) {