diff --git a/CHANGELOG.md b/CHANGELOG.md index 724d6f019..19d177b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -173,3 +173,6 @@ Group changes to describe their impact on the project, as follows: - Crashes Android 6/7 at starting - Permissions issues - Layout of tablet views + +### Changed +- management of the view for displaying call statistics diff --git a/app/src/main/java/org/linphone/call/CallStatsAdapter.java b/app/src/main/java/org/linphone/call/CallStatsAdapter.java new file mode 100644 index 000000000..51162230b --- /dev/null +++ b/app/src/main/java/org/linphone/call/CallStatsAdapter.java @@ -0,0 +1,168 @@ +package org.linphone.call; + +/* +CallStatsAdapter.java +Copyright (C) 2019 Belledonne Communications, Grenoble, France + +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 2 +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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import java.util.ArrayList; +import java.util.List; +import org.linphone.R; +import org.linphone.contacts.ContactsManager; +import org.linphone.contacts.LinphoneContact; +import org.linphone.core.Call; +import org.linphone.utils.LinphoneUtils; +import org.linphone.views.ContactAvatar; + +public class CallStatsAdapter extends BaseExpandableListAdapter { + private final Context mContext; + private List mCalls; + + public CallStatsAdapter(Context mContext) { + this.mContext = mContext; + mCalls = new ArrayList<>(); + } + + public void updateListItems(List listCall) { + if (listCall != null) { + mCalls = listCall; + notifyDataSetChanged(); + } + } + + @Override + public View getChildView( + int groupPosition, + int childPosition, + boolean isLastChild, + View view, + ViewGroup viewGroup) { + + CallStatsChildViewHolder holder; + + if (view != null) { + Object possibleHolder = view.getTag(); + if (possibleHolder instanceof CallStatsChildViewHolder) { + holder = (CallStatsChildViewHolder) possibleHolder; + view.setTag(holder); + } + } else { + // opening the statistics view + LayoutInflater inflater = LayoutInflater.from(mContext); + view = inflater.inflate(R.layout.call_stats_child, viewGroup, false); + } + + // filling the view + holder = new CallStatsChildViewHolder(view, mContext); + view.setTag(holder); + holder.setCall(mCalls.get(groupPosition)); + + return view; + } + + @Override + public View getGroupView( + int groupPosition, boolean isExpanded, View view, ViewGroup viewGroup) { + + CallStatsViewHolder holder = null; + if (view != null) { + Object possibleHolder = view.getTag(); + if (possibleHolder instanceof CallStatsViewHolder) { + holder = (CallStatsViewHolder) possibleHolder; + } + } else { + LayoutInflater inflater = LayoutInflater.from(mContext); + view = inflater.inflate(R.layout.call_stats_group, viewGroup, false); + } + if (holder == null) { + holder = new CallStatsViewHolder(view); + view.setTag(holder); + } + + // Recovering the current call + Call call = (Call) getGroup(groupPosition); + // Search for the associated contact + LinphoneContact contact = + ContactsManager.getInstance().findContactFromAddress(call.getRemoteAddress()); + if (contact != null) { + // Setting up the avatar + ContactAvatar.displayAvatar(contact, holder.avatarLayout); + // addition of the participant's name + holder.participantName.setText(contact.getFullName()); + } else { + String displayName = LinphoneUtils.getAddressDisplayName(call.getRemoteAddress()); + ContactAvatar.displayAvatar(displayName, holder.avatarLayout); + holder.participantName.setText(displayName); + } + + // add sip address on group view + holder.sipUri.setText(call.getRemoteAddress().asString()); + + return view; + } + + @Override + public int getGroupCount() { + return mCalls.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return 1; + } + + @Override + public Object getGroup(int groupPosition) { + if (mCalls.isEmpty() && groupPosition < mCalls.size()) { + return null; + } + return mCalls.get(groupPosition); + } + + @Override + public Object getChild(int groupPosition, int childPosition) { + if (mCalls.isEmpty() && groupPosition < mCalls.size()) { + return null; + } + return mCalls.get(groupPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return groupPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } +} diff --git a/app/src/main/java/org/linphone/call/CallStatsChildViewHolder.java b/app/src/main/java/org/linphone/call/CallStatsChildViewHolder.java new file mode 100644 index 000000000..83c5f27e3 --- /dev/null +++ b/app/src/main/java/org/linphone/call/CallStatsChildViewHolder.java @@ -0,0 +1,396 @@ +package org.linphone.call; + +/* +CallStatsChildViewHolder.java +Copyright (C) 2019 Belledonne Communications, Grenoble, France + +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 2 +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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.content.Context; +import android.os.Handler; +import android.text.Html; +import android.view.View; +import android.widget.TextView; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; +import org.linphone.LinphoneManager; +import org.linphone.R; +import org.linphone.core.AddressFamily; +import org.linphone.core.Call; +import org.linphone.core.CallListenerStub; +import org.linphone.core.CallParams; +import org.linphone.core.CallStats; +import org.linphone.core.PayloadType; +import org.linphone.core.StreamType; + +public class CallStatsChildViewHolder { + private Timer mTimer; + private Call mCall; + private final Handler mHandler = new Handler(); + private CallListenerStub mListener; + private HashMap mEncoderTexts; + private HashMap mDecoderTexts; + private Context mContext; + + private TextView mTitleAudio; + private TextView mTitleVideo; + private TextView mCodecAudio; + private TextView mCodecVideo; + private TextView mEncoderAudio; + private TextView mDecoderAudio; + private TextView mEncoderVideo; + private TextView mDecoderVideo; + private TextView mDisplayFilter; + private TextView mDlAudio; + private TextView mUlAudio; + private TextView mDlVideo; + private TextView mUlVideo; + private TextView mEdlVideo; + private TextView mIceAudio; + private TextView mIceVideo; + private TextView mVideoResolutionSent; + private TextView mVideoResolutionReceived; + private TextView mVideoFpsSent; + private TextView mVideoFpsReceived; + private TextView mSenderLossRateAudio; + private TextView mReceiverLossRateAudio; + private TextView mSenderLossRateVideo; + private TextView mReceiverLossRateVideo; + private TextView mIpAudio; + private TextView mIpVideo; + private TextView mJitterBufferAudio; + private View mVideoLayout; + private View mAudioLayout; + + public CallStatsChildViewHolder(View view, Context context) { + mContext = context; + + mEncoderTexts = new HashMap<>(); + mDecoderTexts = new HashMap<>(); + + mTitleAudio = view.findViewById(R.id.call_stats_audio); + mTitleVideo = view.findViewById(R.id.call_stats_video); + mCodecAudio = view.findViewById(R.id.codec_audio); + mCodecVideo = view.findViewById(R.id.codec_video); + mEncoderAudio = view.findViewById(R.id.encoder_audio); + mDecoderAudio = view.findViewById(R.id.decoder_audio); + mEncoderVideo = view.findViewById(R.id.encoder_video); + mDecoderVideo = view.findViewById(R.id.decoder_video); + mDisplayFilter = view.findViewById(R.id.display_filter); + mDlAudio = view.findViewById(R.id.downloadBandwith_audio); + mUlAudio = view.findViewById(R.id.uploadBandwith_audio); + mDlVideo = view.findViewById(R.id.downloadBandwith_video); + mUlVideo = view.findViewById(R.id.uploadBandwith_video); + mEdlVideo = view.findViewById(R.id.estimatedDownloadBandwidth_video); + mIceAudio = view.findViewById(R.id.ice_audio); + mIceVideo = view.findViewById(R.id.ice_video); + mVideoResolutionSent = view.findViewById(R.id.video_resolution_sent); + mVideoResolutionReceived = view.findViewById(R.id.video_resolution_received); + mVideoFpsSent = view.findViewById(R.id.video_fps_sent); + mVideoFpsReceived = view.findViewById(R.id.video_fps_received); + mSenderLossRateAudio = view.findViewById(R.id.senderLossRateAudio); + mReceiverLossRateAudio = view.findViewById(R.id.receiverLossRateAudio); + mSenderLossRateVideo = view.findViewById(R.id.senderLossRateVideo); + mReceiverLossRateVideo = view.findViewById(R.id.receiverLossRateVideo); + mIpAudio = view.findViewById(R.id.ip_audio); + mIpVideo = view.findViewById(R.id.ip_video); + mJitterBufferAudio = view.findViewById(R.id.jitterBufferAudio); + mVideoLayout = view.findViewById(R.id.callStatsVideo); + mAudioLayout = view.findViewById(R.id.callStatsAudio); + + mListener = + new CallListenerStub() { + public void onStateChanged(Call call, Call.State cstate, String message) { + if (cstate == Call.State.End || cstate == Call.State.Error) { + if (mTimer != null) { + org.linphone.core.tools.Log.i( + "[Call Stats] Call is terminated, stopping mCountDownTimer in charge of stats refreshing."); + mTimer.cancel(); + } + } + } + }; + } + + public void setCall(Call call) { + if (mCall != null) { + mCall.removeListener(mListener); + } + mCall = call; + mCall.addListener(mListener); + + init(); + } + + private void init() { + TimerTask mTask; + + mTimer = new Timer(); + mTask = + new TimerTask() { + @Override + public void run() { + if (mCall == null) { + mTimer.cancel(); + return; + } + + if (mTitleAudio == null + || mCodecAudio == null + || mDlVideo == null + || mEdlVideo == null + || mIceAudio == null + || mVideoResolutionSent == null + || mVideoLayout == null + || mTitleVideo == null + || mIpVideo == null + || mIpAudio == null + || mCodecVideo == null + || mDlAudio == null + || mUlAudio == null + || mUlVideo == null + || mIceVideo == null + || mVideoResolutionReceived == null) { + mTimer.cancel(); + return; + } + + mHandler.post( + new Runnable() { + @Override + public void run() { + if (mCall == null) { + return; + } + + if (mCall.getState() != Call.State.Released) { + CallParams params = mCall.getCurrentParams(); + if (params != null) { + CallStats audioStats = + mCall.getStats(StreamType.Audio); + CallStats videoStats = null; + + if (params.videoEnabled()) + videoStats = mCall.getStats(StreamType.Video); + + PayloadType payloadAudio = + params.getUsedAudioPayloadType(); + PayloadType payloadVideo = + params.getUsedVideoPayloadType(); + + formatText( + mDisplayFilter, + mContext.getString( + R.string.call_stats_display_filter), + mCall.getCore().getVideoDisplayFilter()); + + displayMediaStats( + params, + audioStats, + payloadAudio, + mAudioLayout, + mTitleAudio, + mCodecAudio, + mDlAudio, + mUlAudio, + null, + mIceAudio, + mIpAudio, + mSenderLossRateAudio, + mReceiverLossRateAudio, + mEncoderAudio, + mDecoderAudio, + null, + null, + null, + null, + false, + mJitterBufferAudio); + + displayMediaStats( + params, + videoStats, + payloadVideo, + mVideoLayout, + mTitleVideo, + mCodecVideo, + mDlVideo, + mUlVideo, + mEdlVideo, + mIceVideo, + mIpVideo, + mSenderLossRateVideo, + mReceiverLossRateVideo, + mEncoderVideo, + mDecoderVideo, + mVideoResolutionSent, + mVideoResolutionReceived, + mVideoFpsSent, + mVideoFpsReceived, + true, + null); + } + } + } + }); + } + }; + mTimer.scheduleAtFixedRate(mTask, 0, 1000); + } + + private void displayMediaStats( + CallParams params, + CallStats stats, + PayloadType media, + View layout, + TextView title, + TextView codec, + TextView dl, + TextView ul, + TextView edl, + TextView ice, + TextView ip, + TextView senderLossRate, + TextView receiverLossRate, + TextView enc, + TextView dec, + TextView videoResolutionSent, + TextView videoResolutionReceived, + TextView videoFpsSent, + TextView videoFpsReceived, + boolean isVideo, + TextView jitterBuffer) { + + if (stats != null) { + String mime = null; + + layout.setVisibility(View.VISIBLE); + title.setVisibility(TextView.VISIBLE); + if (media != null) { + mime = media.getMimeType(); + formatText( + codec, + mContext.getString(R.string.call_stats_codec), + mime + " / " + (media.getClockRate() / 1000) + "kHz"); + } + if (mime != null) { + + formatText( + enc, + mContext.getString(R.string.call_stats_encoder_name), + getEncoderText(mime)); + formatText( + dec, + mContext.getString(R.string.call_stats_decoder_name), + getDecoderText(mime)); + } + formatText( + dl, + mContext.getString(R.string.call_stats_download), + (int) stats.getDownloadBandwidth() + " kbits/s"); + formatText( + ul, + mContext.getString(R.string.call_stats_upload), + (int) stats.getUploadBandwidth() + " kbits/s"); + if (isVideo) { + formatText( + edl, + mContext.getString(R.string.call_stats_estimated_download), + stats.getEstimatedDownloadBandwidth() + " kbits/s"); + } + formatText( + ice, + mContext.getString(R.string.call_stats_ice), + stats.getIceState().toString()); + formatText( + ip, + mContext.getString(R.string.call_stats_ip), + (stats.getIpFamilyOfRemote() == AddressFamily.Inet6) + ? "IpV6" + : (stats.getIpFamilyOfRemote() == AddressFamily.Inet) + ? "IpV4" + : "Unknown"); + + formatText( + senderLossRate, + mContext.getString(R.string.call_stats_sender_loss_rate), + new DecimalFormat("##.##").format(stats.getSenderLossRate()) + "%"); + formatText( + receiverLossRate, + mContext.getString(R.string.call_stats_receiver_loss_rate), + new DecimalFormat("##.##").format(stats.getReceiverLossRate()) + "%"); + + if (isVideo) { + formatText( + videoResolutionSent, + mContext.getString(R.string.call_stats_video_resolution_sent), + "\u2191 " + params.getSentVideoDefinition() != null + ? params.getSentVideoDefinition().getName() + : ""); + formatText( + videoResolutionReceived, + mContext.getString(R.string.call_stats_video_resolution_received), + "\u2193 " + params.getReceivedVideoDefinition() != null + ? params.getReceivedVideoDefinition().getName() + : ""); + formatText( + videoFpsSent, + mContext.getString(R.string.call_stats_video_fps_sent), + "\u2191 " + params.getSentFramerate()); + formatText( + videoFpsReceived, + mContext.getString(R.string.call_stats_video_fps_received), + "\u2193 " + params.getReceivedFramerate()); + } else { + formatText( + jitterBuffer, + mContext.getString(R.string.call_stats_jitter_buffer), + new DecimalFormat("##.##").format(stats.getJitterBufferSizeMs()) + " ms"); + } + } else { + layout.setVisibility(View.GONE); + title.setVisibility(TextView.GONE); + } + } + + private String getEncoderText(String mime) { + String ret = mEncoderTexts.get(mime); + if (ret == null) { + org.linphone.mediastream.Factory msfactory = + LinphoneManager.getCore().getMediastreamerFactory(); + ret = msfactory.getEncoderText(mime); + mEncoderTexts.put(mime, ret); + } + return ret; + } + + private String getDecoderText(String mime) { + String ret = mDecoderTexts.get(mime); + if (ret == null) { + org.linphone.mediastream.Factory msfactory = + LinphoneManager.getCore().getMediastreamerFactory(); + ret = msfactory.getDecoderText(mime); + mDecoderTexts.put(mime, ret); + } + return ret; + } + + private void formatText(TextView tv, String name, String value) { + tv.setText(Html.fromHtml("" + name + " " + value)); + } +} diff --git a/app/src/main/java/org/linphone/call/CallStatsFragment.java b/app/src/main/java/org/linphone/call/CallStatsFragment.java index 6c0019fa8..6d85786be 100644 --- a/app/src/main/java/org/linphone/call/CallStatsFragment.java +++ b/app/src/main/java/org/linphone/call/CallStatsFragment.java @@ -21,87 +21,75 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import android.app.Fragment; import android.os.Bundle; -import android.os.Handler; -import android.text.Html; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ExpandableListView; import android.widget.RelativeLayout; -import android.widget.TextView; import androidx.annotation.Nullable; import androidx.drawerlayout.widget.DrawerLayout; -import java.text.DecimalFormat; -import java.util.HashMap; -import java.util.Timer; -import java.util.TimerTask; +import java.util.Arrays; import org.linphone.LinphoneManager; import org.linphone.R; -import org.linphone.core.AddressFamily; import org.linphone.core.Call; -import org.linphone.core.CallListenerStub; -import org.linphone.core.CallParams; -import org.linphone.core.CallStats; import org.linphone.core.Core; -import org.linphone.core.PayloadType; -import org.linphone.core.StreamType; -import org.linphone.core.tools.Log; +import org.linphone.core.CoreListenerStub; public class CallStatsFragment extends Fragment { - private final Handler mHandler = new Handler(); - private Timer mTimer; - private TimerTask mTask; - - private View mView; private DrawerLayout mSideMenu; - private RelativeLayout mSideMenuContent; - - private HashMap mEncoderTexts; - private HashMap mDecoderTexts; - - private Call mCall; - private CallListenerStub mListener; + private RelativeLayout mSideMenuContent;; + private ExpandableListView mExpandableList; + private CallStatsAdapter mAdapter; + private CoreListenerStub mListener; @Nullable @Override public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.call_stats, container, false); - mView = view; - mEncoderTexts = new HashMap<>(); - mDecoderTexts = new HashMap<>(); + mExpandableList = view.findViewById(R.id.devices_list); mListener = - new CallListenerStub() { - public void onStateChanged(Call call, Call.State cstate, String message) { + new CoreListenerStub() { + @Override + public void onCallStateChanged( + Core lc, Call call, Call.State cstate, String message) { if (cstate == Call.State.End || cstate == Call.State.Error) { - if (mTimer != null) { - Log.i( - "[Call Stats] Call is terminated, stopping mCountDownTimer in charge of stats refreshing."); - mTimer.cancel(); - } + mAdapter.updateListItems( + Arrays.asList(LinphoneManager.getCore().getCalls())); } } }; - return view; } @Override public void onResume() { super.onResume(); - Core core = LinphoneManager.getCore(); - if (core != null) { - setCall(core.getCurrentCall()); + + if (mAdapter == null) { + mAdapter = new CallStatsAdapter(getActivity()); + + mExpandableList.setAdapter(mAdapter); + // allows you to open the first child in the list + mExpandableList.expandGroup(0); } + + // Sends calls from the list to the adapter + if (core != null && core.getCallsNb() >= 1) { + mAdapter.updateListItems(Arrays.asList(core.getCalls())); + } + + core.addListener(mListener); } @Override public void onPause() { - setCall(null); super.onPause(); + LinphoneManager.getCore().removeListener(mListener); } public void setDrawer(DrawerLayout drawer, RelativeLayout content) { @@ -131,294 +119,4 @@ public class CallStatsFragment extends Fragment { mSideMenu.closeDrawer(mSideMenuContent, animate); } } - - public void setCall(Call call) { - if (mCall != null) { - mCall.removeListener(mListener); - } - mCall = call; - init(mView); - } - - private void init(View view) { - if (getResources().getBoolean(R.bool.hide_in_call_stats)) return; - - if (mTimer != null && mTask != null) { - mTimer.cancel(); - mTimer = null; - mTask = null; - } - if (mCall == null) { - return; - } - - final TextView titleAudio = view.findViewById(R.id.call_stats_audio); - final TextView titleVideo = view.findViewById(R.id.call_stats_video); - final TextView codecAudio = view.findViewById(R.id.codec_audio); - final TextView codecVideo = view.findViewById(R.id.codec_video); - final TextView encoderAudio = view.findViewById(R.id.encoder_audio); - final TextView decoderAudio = view.findViewById(R.id.decoder_audio); - final TextView encoderVideo = view.findViewById(R.id.encoder_video); - final TextView decoderVideo = view.findViewById(R.id.decoder_video); - final TextView displayFilter = view.findViewById(R.id.display_filter); - final TextView dlAudio = view.findViewById(R.id.downloadBandwith_audio); - final TextView ulAudio = view.findViewById(R.id.uploadBandwith_audio); - final TextView dlVideo = view.findViewById(R.id.downloadBandwith_video); - final TextView ulVideo = view.findViewById(R.id.uploadBandwith_video); - final TextView edlVideo = view.findViewById(R.id.estimatedDownloadBandwidth_video); - final TextView iceAudio = view.findViewById(R.id.ice_audio); - final TextView iceVideo = view.findViewById(R.id.ice_video); - final TextView videoResolutionSent = view.findViewById(R.id.video_resolution_sent); - final TextView videoResolutionReceived = view.findViewById(R.id.video_resolution_received); - final TextView videoFpsSent = view.findViewById(R.id.video_fps_sent); - final TextView videoFpsReceived = view.findViewById(R.id.video_fps_received); - final TextView senderLossRateAudio = view.findViewById(R.id.senderLossRateAudio); - final TextView receiverLossRateAudio = view.findViewById(R.id.receiverLossRateAudio); - final TextView senderLossRateVideo = view.findViewById(R.id.senderLossRateVideo); - final TextView receiverLossRateVideo = view.findViewById(R.id.receiverLossRateVideo); - final TextView ipAudio = view.findViewById(R.id.ip_audio); - final TextView ipVideo = view.findViewById(R.id.ip_video); - final TextView jitterBufferAudio = view.findViewById(R.id.jitterBufferAudio); - final View videoLayout = view.findViewById(R.id.callStatsVideo); - final View audioLayout = view.findViewById(R.id.callStatsAudio); - - mTimer = new Timer(); - mTask = - new TimerTask() { - @Override - public void run() { - if (mCall == null) { - mTimer.cancel(); - return; - } - - if (titleAudio == null - || codecAudio == null - || dlVideo == null - || edlVideo == null - || iceAudio == null - || videoResolutionSent == null - || videoLayout == null - || titleVideo == null - || ipVideo == null - || ipAudio == null - || codecVideo == null - || dlAudio == null - || ulAudio == null - || ulVideo == null - || iceVideo == null - || videoResolutionReceived == null) { - mTimer.cancel(); - return; - } - - mHandler.post( - new Runnable() { - @Override - public void run() { - if (mCall == null) return; - - if (mCall.getState() != Call.State.Released) { - CallParams params = mCall.getCurrentParams(); - if (params != null) { - CallStats audioStats = - mCall.getStats(StreamType.Audio); - CallStats videoStats = null; - - if (params.videoEnabled()) - videoStats = mCall.getStats(StreamType.Video); - - PayloadType payloadAudio = - params.getUsedAudioPayloadType(); - PayloadType payloadVideo = - params.getUsedVideoPayloadType(); - - formatText( - displayFilter, - getString( - R.string.call_stats_display_filter), - mCall.getCore().getVideoDisplayFilter()); - - displayMediaStats( - params, - audioStats, - payloadAudio, - audioLayout, - titleAudio, - codecAudio, - dlAudio, - ulAudio, - null, - iceAudio, - ipAudio, - senderLossRateAudio, - receiverLossRateAudio, - encoderAudio, - decoderAudio, - null, - null, - null, - null, - false, - jitterBufferAudio); - - displayMediaStats( - params, - videoStats, - payloadVideo, - videoLayout, - titleVideo, - codecVideo, - dlVideo, - ulVideo, - edlVideo, - iceVideo, - ipVideo, - senderLossRateVideo, - receiverLossRateVideo, - encoderVideo, - decoderVideo, - videoResolutionSent, - videoResolutionReceived, - videoFpsSent, - videoFpsReceived, - true, - null); - } - } - } - }); - } - }; - mCall.addListener(mListener); - mTimer.scheduleAtFixedRate(mTask, 0, 1000); - } - - private void formatText(TextView tv, String name, String value) { - tv.setText(Html.fromHtml("" + name + " " + value)); - } - - private String getEncoderText(String mime) { - String ret = mEncoderTexts.get(mime); - if (ret == null) { - org.linphone.mediastream.Factory msfactory = - LinphoneManager.getCore().getMediastreamerFactory(); - ret = msfactory.getEncoderText(mime); - mEncoderTexts.put(mime, ret); - } - return ret; - } - - private String getDecoderText(String mime) { - String ret = mDecoderTexts.get(mime); - if (ret == null) { - org.linphone.mediastream.Factory msfactory = - LinphoneManager.getCore().getMediastreamerFactory(); - ret = msfactory.getDecoderText(mime); - mDecoderTexts.put(mime, ret); - } - return ret; - } - - private void displayMediaStats( - CallParams params, - CallStats stats, - PayloadType media, - View layout, - TextView title, - TextView codec, - TextView dl, - TextView ul, - TextView edl, - TextView ice, - TextView ip, - TextView senderLossRate, - TextView receiverLossRate, - TextView enc, - TextView dec, - TextView videoResolutionSent, - TextView videoResolutionReceived, - TextView videoFpsSent, - TextView videoFpsReceived, - boolean isVideo, - TextView jitterBuffer) { - if (stats != null) { - String mime = null; - - layout.setVisibility(View.VISIBLE); - title.setVisibility(TextView.VISIBLE); - if (media != null) { - mime = media.getMimeType(); - formatText( - codec, - getString(R.string.call_stats_codec), - mime + " / " + (media.getClockRate() / 1000) + "kHz"); - } - if (mime != null) { - formatText(enc, getString(R.string.call_stats_encoder_name), getEncoderText(mime)); - formatText(dec, getString(R.string.call_stats_decoder_name), getDecoderText(mime)); - } - formatText( - dl, - getString(R.string.call_stats_download), - (int) stats.getDownloadBandwidth() + " kbits/s"); - formatText( - ul, - getString(R.string.call_stats_upload), - (int) stats.getUploadBandwidth() + " kbits/s"); - if (isVideo) { - formatText( - edl, - getString(R.string.call_stats_estimated_download), - stats.getEstimatedDownloadBandwidth() + " kbits/s"); - } - formatText(ice, getString(R.string.call_stats_ice), stats.getIceState().toString()); - formatText( - ip, - getString(R.string.call_stats_ip), - (stats.getIpFamilyOfRemote() == AddressFamily.Inet6) - ? "IpV6" - : (stats.getIpFamilyOfRemote() == AddressFamily.Inet) - ? "IpV4" - : "Unknown"); - formatText( - senderLossRate, - getString(R.string.call_stats_sender_loss_rate), - new DecimalFormat("##.##").format(stats.getSenderLossRate()) + "%"); - formatText( - receiverLossRate, - getString(R.string.call_stats_receiver_loss_rate), - new DecimalFormat("##.##").format(stats.getReceiverLossRate()) + "%"); - if (isVideo) { - formatText( - videoResolutionSent, - getString(R.string.call_stats_video_resolution_sent), - "\u2191 " + params.getSentVideoDefinition() != null - ? params.getSentVideoDefinition().getName() - : ""); - formatText( - videoResolutionReceived, - getString(R.string.call_stats_video_resolution_received), - "\u2193 " + params.getReceivedVideoDefinition() != null - ? params.getReceivedVideoDefinition().getName() - : ""); - formatText( - videoFpsSent, - getString(R.string.call_stats_video_fps_sent), - "\u2191 " + params.getSentFramerate()); - formatText( - videoFpsReceived, - getString(R.string.call_stats_video_fps_received), - "\u2193 " + params.getReceivedFramerate()); - } else { - formatText( - jitterBuffer, - getString(R.string.call_stats_jitter_buffer), - new DecimalFormat("##.##").format(stats.getJitterBufferSizeMs()) + " ms"); - } - } else { - layout.setVisibility(View.GONE); - title.setVisibility(TextView.GONE); - } - } } diff --git a/app/src/main/java/org/linphone/call/CallStatsViewHolder.java b/app/src/main/java/org/linphone/call/CallStatsViewHolder.java new file mode 100644 index 000000000..878299815 --- /dev/null +++ b/app/src/main/java/org/linphone/call/CallStatsViewHolder.java @@ -0,0 +1,37 @@ +package org.linphone.call; + +/* +CallStatsViewHolder.java +Copyright (C) 2019 Belledonne Communications, Grenoble, France + +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 2 +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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; +import org.linphone.R; + +public class CallStatsViewHolder { + + public final RelativeLayout avatarLayout; + public final TextView participantName, sipUri; + + public CallStatsViewHolder(View v) { + avatarLayout = v.findViewById(R.id.avatar_layout); + participantName = v.findViewById(R.id.name); + sipUri = v.findViewById(R.id.sipUri); + } +} diff --git a/app/src/main/res/layout-land/call_stats_child.xml b/app/src/main/res/layout-land/call_stats_child.xml new file mode 100644 index 000000000..095503ab9 --- /dev/null +++ b/app/src/main/res/layout-land/call_stats_child.xml @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_stats.xml b/app/src/main/res/layout/call_stats.xml index 24f59753d..e15b4ae4d 100644 --- a/app/src/main/res/layout/call_stats.xml +++ b/app/src/main/res/layout/call_stats.xml @@ -1,285 +1,15 @@ - - + android:layout_height="match_parent" + android:divider="?attr/dividerColor" + android:childDivider="?attr/dividerColor" + android:dividerHeight="1dp" + android:groupIndicator="@null" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/call_stats_child.xml b/app/src/main/res/layout/call_stats_child.xml new file mode 100644 index 000000000..70be276c4 --- /dev/null +++ b/app/src/main/res/layout/call_stats_child.xml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/call_stats_group.xml b/app/src/main/res/layout/call_stats_group.xml new file mode 100644 index 000000000..76c5f131a --- /dev/null +++ b/app/src/main/res/layout/call_stats_group.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file