blob: 9827fb69c79169427fa529969dad381d099b968b [file] [log] [blame]
/*
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
*
* Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
* 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.fragments;
import android.Manifest;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.util.Rational;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.view.menu.MenuPopupHelper;
import androidx.appcompat.widget.PopupMenu;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.FragmentActivity;
import androidx.percentlayout.widget.PercentFrameLayout;
import com.rodolfonavalon.shaperipplelibrary.model.Circle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import javax.inject.Inject;
import cx.ring.R;
import cx.ring.adapters.ConfParticipantAdapter;
import cx.ring.application.JamiApplication;
import cx.ring.call.CallPresenter;
import cx.ring.call.CallView;
import cx.ring.client.CallActivity;
import cx.ring.client.ContactDetailsActivity;
import cx.ring.client.ConversationActivity;
import cx.ring.client.ConversationSelectionActivity;
import cx.ring.client.HomeActivity;
import cx.ring.daemon.Ringservice;
import cx.ring.databinding.FragCallBinding;
import cx.ring.databinding.ItemParticipantLabelBinding;
import cx.ring.model.CallContact;
import cx.ring.model.Conference;
import cx.ring.model.SipCall;
import cx.ring.mvp.BaseSupportFragment;
import cx.ring.plugins.RecyclerPicker.RecyclerPicker;
import cx.ring.plugins.RecyclerPicker.RecyclerPickerLayoutManager;
import cx.ring.service.DRingService;
import cx.ring.services.DeviceRuntimeService;
import cx.ring.services.HardwareService;
import cx.ring.services.NotificationService;
import cx.ring.utils.ActionHelper;
import cx.ring.utils.ContentUriHandler;
import cx.ring.utils.ConversationPath;
import cx.ring.utils.DeviceUtils;
import cx.ring.utils.KeyboardVisibilityManager;
import cx.ring.utils.MediaButtonsHelper;
import cx.ring.utils.StringUtils;
import cx.ring.views.AvatarDrawable;
import io.reactivex.disposables.CompositeDisposable;
import static cx.ring.daemon.Ringservice.getPluginsEnabled;
public class CallFragment extends BaseSupportFragment<CallPresenter> implements CallView, MediaButtonsHelper.MediaButtonsHelperCallback, RecyclerPickerLayoutManager.ItemSelectedListener {
public static final String TAG = CallFragment.class.getSimpleName();
public static final String ACTION_PLACE_CALL = "PLACE_CALL";
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";
private static final int REQUEST_CODE_ADD_PARTICIPANT = 6;
private static final int REQUEST_PERMISSION_INCOMING = 1003;
private static final int REQUEST_PERMISSION_OUTGOING = 1004;
private static final int REQUEST_CODE_SCREEN_SHARE = 7;
private FragCallBinding binding;
private OrientationEventListener mOrientationListener;
private MenuItem dialPadBtn = null;
private MenuItem pluginsMenuBtn = null;
private boolean restartVideo = false;
private boolean restartPreview = false;
private PowerManager.WakeLock mScreenWakeLock = null;
private int mCurrentOrientation = 0;
private int mVideoWidth = -1;
private int mVideoHeight = -1;
private int mPreviewWidth = 720, mPreviewHeight = 1280;
private int mPreviewSurfaceWidth = 0, mPreviewSurfaceHeight = 0;
private MediaProjectionManager mProjectionManager;
private boolean mBackstackLost = false;
private ConfParticipantAdapter confAdapter = null;
private boolean mConferenceMode = false;
private boolean choosePluginMode = false;
public boolean isChoosePluginMode() {
return choosePluginMode;
}
private boolean pluginsModeFirst = true;
private List<String> callMediaHandlers;
private int previousPluginPosition = -1;
private RecyclerPicker rp;
private final ValueAnimator animation = new ValueAnimator();
private PointF previewDrag = null;
private final ValueAnimator previewSnapAnimation = new ValueAnimator();
private final int[] previewMargins = new int[4];
private float previewHiddenState = 0;
private enum PreviewPosition {LEFT, RIGHT}
private PreviewPosition previewPosition = PreviewPosition.RIGHT;
@Inject
DeviceRuntimeService mDeviceRuntimeService;
private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
public static CallFragment newInstance(@NonNull String action, @Nullable String accountID, @Nullable String contactRingId, boolean audioOnly) {
Bundle bundle = new Bundle();
bundle.putString(KEY_ACTION, action);
bundle.putString(KEY_ACCOUNT_ID, accountID);
bundle.putString(ConversationFragment.KEY_CONTACT_RING_ID, contactRingId);
bundle.putBoolean(KEY_AUDIO_ONLY, audioOnly);
CallFragment countDownFragment = new CallFragment();
countDownFragment.setArguments(bundle);
return countDownFragment;
}
public static CallFragment newInstance(@NonNull String action, @Nullable String confId) {
Bundle bundle = new Bundle();
bundle.putString(KEY_ACTION, action);
bundle.putString(KEY_CONF_ID, confId);
CallFragment countDownFragment = new CallFragment();
countDownFragment.setArguments(bundle);
return countDownFragment;
}
public static int callStateToHumanState(final SipCall.CallStatus state) {
switch (state) {
case SEARCHING:
return R.string.call_human_state_searching;
case CONNECTING:
return R.string.call_human_state_connecting;
case RINGING:
return R.string.call_human_state_ringing;
case CURRENT:
return R.string.call_human_state_current;
case HUNGUP:
return R.string.call_human_state_hungup;
case BUSY:
return R.string.call_human_state_busy;
case FAILURE:
return R.string.call_human_state_failure;
case HOLD:
return R.string.call_human_state_hold;
case UNHOLD:
return R.string.call_human_state_unhold;
case OVER:
return R.string.call_human_state_over;
case NONE:
default:
return R.string.call_human_state_none;
}
}
@Override
protected void initPresenter(CallPresenter presenter) {
super.initPresenter(presenter);
Bundle args = getArguments();
if (args != null) {
String action = args.getString(KEY_ACTION);
if (action != null) {
if (action.equals(ACTION_PLACE_CALL)) {
prepareCall(false);
} else if (action.equals(ACTION_GET_CALL) || action.equals(CallActivity.ACTION_CALL_ACCEPT)) {
presenter.initIncomingCall(getArguments().getString(KEY_CONF_ID), action.equals(ACTION_GET_CALL));
}
}
}
}
public void onUserLeave() {
presenter.requestPipMode();
}
@Override
public void enterPipMode(String callId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return;
}
Context context = requireContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PictureInPictureParams.Builder paramBuilder = new PictureInPictureParams.Builder();
if (binding.videoSurface.getVisibility() == View.VISIBLE) {
int[] l = new int[2];
binding.videoSurface.getLocationInWindow(l);
int x = l[0];
int y = l[1];
int w = binding.videoSurface.getWidth();
int h = binding.videoSurface.getHeight();
Rect videoBounds = new Rect(x, y, x + w, y + h);
paramBuilder.setAspectRatio(new Rational(w, h));
paramBuilder.setSourceRectHint(videoBounds);
}
ArrayList<RemoteAction> actions = new ArrayList<>(1);
actions.add(new RemoteAction(
Icon.createWithResource(context, R.drawable.baseline_call_end_24),
getString(R.string.action_call_hangup),
getString(R.string.action_call_hangup),
PendingIntent.getService(context, new Random().nextInt(),
new Intent(DRingService.ACTION_CALL_END)
.setClass(context, DRingService.class)
.putExtra(NotificationService.KEY_CALL_ID, callId), PendingIntent.FLAG_ONE_SHOT)));
paramBuilder.setActions(actions);
requireActivity().enterPictureInPictureMode(paramBuilder.build());
} else if (DeviceUtils.isTv(context)) {
requireActivity().enterPictureInPictureMode();
}
}
@Override
public void onStart() {
super.onStart();
if (restartVideo && restartPreview) {
displayVideoSurface(true, !presenter.isPipMode());
restartVideo = false;
restartPreview = false;
} else if (restartVideo) {
displayVideoSurface(true, false);
restartVideo = false;
}
}
@Override
public void onStop() {
super.onStop();
previewSnapAnimation.cancel();
if (binding.videoSurface.getVisibility() == View.VISIBLE) {
restartVideo = true;
}
if (!choosePluginMode) {
if (binding.previewContainer.getVisibility() == View.VISIBLE) {
restartPreview = true;
}
}else {
if (binding.pluginPreviewContainer.getVisibility() == View.VISIBLE) {
restartPreview = true;
presenter.stopPlugin();
}
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
binding = DataBindingUtil.inflate(inflater, R.layout.frag_call, container, false);
binding.setPresenter(this);
rp = new RecyclerPicker(binding.recyclerPicker,
R.layout.item_picker,
LinearLayout.HORIZONTAL, this);
rp.setFirstLastElementsWidths(112, 112);
return binding.getRoot();
}
private TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mPreviewSurfaceWidth = width;
mPreviewSurfaceHeight = height;
presenter.previewVideoSurfaceCreated(binding.previewSurface);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
mPreviewSurfaceWidth = width;
mPreviewSurfaceHeight = height;
configurePreview(width, 1);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
presenter.previewVideoSurfaceDestroyed();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
};
/**
* @param hiddenState 0.f if fully shown, 1.f if fully hidden.
*/
private void setPreviewDragHiddenState(float hiddenState) {
binding.previewSurface.setAlpha(1.f - (3 * hiddenState / 4));
binding.pluginPreviewSurface.setAlpha(1.f - (3 * hiddenState / 4));
binding.previewHandle.setAlpha(hiddenState);
binding.pluginPreviewHandle.setAlpha(hiddenState);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
setHasOptionsMenu(true);
super.onViewCreated(view, savedInstanceState);
mCurrentOrientation = getResources().getConfiguration().orientation;
float dpRatio = requireActivity().getResources().getDisplayMetrics().density;
animation.setDuration(150);
animation.addUpdateListener(valueAnimator -> {
if (binding == null)
return;
int upBy = (int) valueAnimator.getAnimatedValue();
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) binding.previewContainer.getLayoutParams();
layoutParams.setMargins(0, 0, 0, (int) (upBy * dpRatio));
binding.previewContainer.setLayoutParams(layoutParams);
});
FragmentActivity activity = getActivity();
if (activity != null) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (activity instanceof AppCompatActivity) {
AppCompatActivity ac_activity = (AppCompatActivity) activity;
ActionBar ab = ac_activity.getSupportActionBar();
if (ab != null) {
ab.setHomeAsUpIndicator(R.drawable.baseline_chat_24);
ab.setDisplayHomeAsUpEnabled(true);
}
}
}
mProjectionManager = (MediaProjectionManager) requireContext().getSystemService(Context.MEDIA_PROJECTION_SERVICE);
PowerManager powerManager = (PowerManager) requireContext().getSystemService(Context.POWER_SERVICE);
if (powerManager != null) {
mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "ring:callLock");
mScreenWakeLock.setReferenceCounted(false);
if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
mScreenWakeLock.acquire();
}
}
if (presenter.setBlockRecordStatus()) {
binding.videoSurface.setSecure(true);
getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
binding.videoSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
binding.videoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
presenter.videoSurfaceCreated(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
presenter.videoSurfaceDestroyed();
}
});
binding.pluginPreviewSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
binding.pluginPreviewSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
presenter.pluginSurfaceCreated(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
presenter.pluginSurfaceDestroyed();
}
});
view.setOnSystemUiVisibilityChangeListener(visibility -> {
boolean ui = (visibility & (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN)) == 0;
presenter.uiVisibilityChanged(ui);
});
view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
resetVideoSize(mVideoWidth, mVideoHeight));
WindowManager windowManager = (WindowManager) requireContext().getSystemService(Context.WINDOW_SERVICE);
if (windowManager != null) {
mOrientationListener = new OrientationEventListener(getContext()) {
@Override
public void onOrientationChanged(int orientation) {
int rot = windowManager.getDefaultDisplay().getRotation();
if (mCurrentOrientation != rot) {
mCurrentOrientation = rot;
presenter.configurationChanged(rot);
}
}
};
if (mOrientationListener.canDetectOrientation()) {
mOrientationListener.enable();
}
}
binding.shapeRipple.setRippleShape(new Circle());
binding.callSpeakerBtn.setChecked(presenter.isSpeakerphoneOn());
binding.callMicBtn.setChecked(presenter.isMicrophoneMuted());
binding.pluginPreviewSurface.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
configureTransform(mPreviewSurfaceWidth, mPreviewSurfaceHeight));
binding.previewSurface.setSurfaceTextureListener(listener);
binding.previewSurface.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
configureTransform(mPreviewSurfaceWidth, mPreviewSurfaceHeight));
previewSnapAnimation.setDuration(250);
previewSnapAnimation.setFloatValues(0.f, 1.f);
previewSnapAnimation.setInterpolator(new DecelerateInterpolator());
previewSnapAnimation.addUpdateListener(animation -> {
float animatedFraction = animation == null ? 1 : animation.getAnimatedFraction();
configurePreview(mPreviewSurfaceWidth, animatedFraction);
});
binding.previewContainer.setOnTouchListener((v, event) -> {
int action = event.getActionMasked();
RelativeLayout parent = (RelativeLayout) v.getParent();
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) v.getLayoutParams();
if (action == MotionEvent.ACTION_DOWN) {
previewSnapAnimation.cancel();
previewDrag = new PointF(event.getX(), event.getY());
v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation_dragged));
params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.setMargins((int) v.getX(), (int) v.getY(), parent.getWidth() - ((int) v.getX() + v.getWidth()), parent.getHeight() - ((int) v.getY() + v.getHeight()));
v.setLayoutParams(params);
return true;
} else if (action == MotionEvent.ACTION_MOVE) {
if (previewDrag != null) {
int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
int currentYPosition = params.topMargin + (int) (event.getY() - previewDrag.y);
params.setMargins(
currentXPosition,
currentYPosition,
-((currentXPosition + v.getWidth()) - (int) event.getX()),
-((currentYPosition + v.getHeight()) - (int) event.getY()));
v.setLayoutParams(params);
float outPosition = binding.previewContainer.getWidth() * 0.85f;
float drapOut = 0.f;
if (currentXPosition < 0) {
drapOut = Math.min(1.f, -currentXPosition / outPosition);
} else if (currentXPosition + v.getWidth() > parent.getWidth()) {
drapOut = Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition);
}
setPreviewDragHiddenState(drapOut);
return true;
}
return false;
} else if (action == MotionEvent.ACTION_UP) {
if (previewDrag != null) {
int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
previewSnapAnimation.cancel();
previewDrag = null;
v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation));
int ml = 0, mr = 0, mt = 0, mb = 0;
FrameLayout.LayoutParams hp = (FrameLayout.LayoutParams) binding.previewHandle.getLayoutParams();
if (params.leftMargin + (v.getWidth() / 2) > parent.getWidth() / 2) {
params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
mr = (int) (parent.getWidth() - v.getWidth() - v.getX());
previewPosition = PreviewPosition.RIGHT;
hp.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT;
} else {
params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
ml = (int) v.getX();
previewPosition = PreviewPosition.LEFT;
hp.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
}
binding.previewHandle.setLayoutParams(hp);
if (params.topMargin + (v.getHeight() / 2) > parent.getHeight() / 2) {
params.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
mb = (int) (parent.getHeight() - v.getHeight() - v.getY());
} else {
params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
mt = (int) v.getY();
}
previewMargins[0] = ml;
previewMargins[1] = mt;
previewMargins[2] = mr;
previewMargins[3] = mb;
params.setMargins(ml, mt, mr, mb);
v.setLayoutParams(params);
float outPosition = binding.previewContainer.getWidth() * 0.85f;
previewHiddenState = currentXPosition < 0
? Math.min(1.f, -currentXPosition / outPosition)
: ((currentXPosition + v.getWidth() > parent.getWidth())
? Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition)
: 0.f);
setPreviewDragHiddenState(previewHiddenState);
previewSnapAnimation.start();
return true;
}
return false;
} else {
return false;
}
});
binding.pluginPreviewContainer.setOnTouchListener((v, event) -> {
int action = event.getActionMasked();
RelativeLayout parent = (RelativeLayout) v.getParent();
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) v.getLayoutParams();
if (action == MotionEvent.ACTION_DOWN) {
previewSnapAnimation.cancel();
previewDrag = new PointF(event.getX(), event.getY());
v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation_dragged));
params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.setMargins((int) v.getX(), (int) v.getY(), parent.getWidth() - ((int) v.getX() + v.getWidth()), parent.getHeight() - ((int) v.getY() + v.getHeight()));
v.setLayoutParams(params);
return true;
} else if (action == MotionEvent.ACTION_MOVE) {
if (previewDrag != null) {
int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
int currentYPosition = params.topMargin + (int) (event.getY() - previewDrag.y);
params.setMargins(
currentXPosition,
currentYPosition,
-((currentXPosition + v.getWidth()) - (int) event.getX()),
-((currentYPosition + v.getHeight()) - (int) event.getY()));
v.setLayoutParams(params);
float outPosition = binding.pluginPreviewContainer.getWidth() * 0.85f;
float drapOut = 0.f;
if (currentXPosition < 0) {
drapOut = Math.min(1.f, -currentXPosition / outPosition);
} else if (currentXPosition + v.getWidth() > parent.getWidth()) {
drapOut = Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition);
}
setPreviewDragHiddenState(drapOut);
return true;
}
return false;
} else if (action == MotionEvent.ACTION_UP) {
if (previewDrag != null) {
int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
previewSnapAnimation.cancel();
previewDrag = null;
v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation));
int ml = 0, mr = 0, mt = 0, mb = 0;
FrameLayout.LayoutParams hp = (FrameLayout.LayoutParams) binding.pluginPreviewHandle.getLayoutParams();
if (params.leftMargin + (v.getWidth() / 2) > parent.getWidth() / 2) {
params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
mr = (int) (parent.getWidth() - v.getWidth() - v.getX());
previewPosition = PreviewPosition.RIGHT;
hp.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT;
} else {
params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
ml = (int) v.getX();
previewPosition = PreviewPosition.LEFT;
hp.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
}
binding.pluginPreviewHandle.setLayoutParams(hp);
if (params.topMargin + (v.getHeight() / 2) > parent.getHeight() / 2) {
params.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
mb = (int) (parent.getHeight() - v.getHeight() - v.getY());
} else {
params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
mt = (int) v.getY();
}
previewMargins[0] = ml;
previewMargins[1] = mt;
previewMargins[2] = mr;
previewMargins[3] = mb;
params.setMargins(ml, mt, mr, mb);
v.setLayoutParams(params);
float outPosition = binding.pluginPreviewContainer.getWidth() * 0.85f;
previewHiddenState = currentXPosition < 0
? Math.min(1.f, -currentXPosition / outPosition)
: ((currentXPosition + v.getWidth() > parent.getWidth())
? Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition)
: 0.f);
setPreviewDragHiddenState(previewHiddenState);
previewSnapAnimation.start();
return true;
}
return false;
} else {
return false;
}
});
binding.dialpadEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
presenter.sendDtmf(s.subSequence(start, start + count));
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
private void configurePreview(int width, float animatedFraction) {
Context context = getContext();
if (context == null || binding == null)
return;
float margin = context.getResources().getDimension(R.dimen.call_preview_margin);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.previewContainer.getLayoutParams();
float r = 1.f - animatedFraction;
float hideMargin = 0.f;
float targetHiddenState = 0.f;
if (previewHiddenState > 0.f) {
targetHiddenState = 1.f;
float v = width * 0.85f * animatedFraction;
hideMargin = previewPosition == PreviewPosition.RIGHT ? v : -v;
}
setPreviewDragHiddenState(previewHiddenState * r + targetHiddenState * animatedFraction);
float f = margin * animatedFraction;
params.setMargins(
(int) (previewMargins[0] * r + f + hideMargin),
(int) (previewMargins[1] * r + f),
(int) (previewMargins[2] * r + f - hideMargin),
(int) (previewMargins[3] * r + f));
binding.previewContainer.setLayoutParams(params);
binding.pluginPreviewContainer.setLayoutParams(params);
}
/**
* Releases current wakelock and acquires a new proximity wakelock if current call is audio only.
*
* @param isAudioOnly true if it is an audio call
*/
@Override
public void handleCallWakelock(boolean isAudioOnly) {
if (isAudioOnly) {
if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
mScreenWakeLock.release();
}
PowerManager powerManager = (PowerManager) requireContext().getSystemService(Context.POWER_SERVICE);
if (powerManager != null) {
mScreenWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "ring:callLock");
mScreenWakeLock.setReferenceCounted(false);
if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
mScreenWakeLock.acquire();
}
}
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mOrientationListener != null) {
mOrientationListener.disable();
mOrientationListener = null;
}
mCompositeDisposable.clear();
if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
mScreenWakeLock.release();
}
binding = null;
}
@Override
public void onDestroy() {
super.onDestroy();
mCompositeDisposable.dispose();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode != REQUEST_PERMISSION_INCOMING && requestCode != REQUEST_PERMISSION_OUTGOING)
return;
for (int i = 0, n = permissions.length; i < n; i++) {
boolean audioGranted = mDeviceRuntimeService.hasAudioPermission();
boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
switch (permissions[i]) {
case Manifest.permission.CAMERA:
presenter.cameraPermissionChanged(granted);
if (audioGranted) {
initializeCall(requestCode == REQUEST_PERMISSION_INCOMING);
}
break;
case Manifest.permission.RECORD_AUDIO:
presenter.audioPermissionChanged(granted);
initializeCall(requestCode == REQUEST_PERMISSION_INCOMING);
break;
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_CODE_ADD_PARTICIPANT) {
if (resultCode == Activity.RESULT_OK && data != null) {
ConversationPath path = ConversationPath.fromUri(data.getData());
if (path != null) {
presenter.addConferenceParticipant(path.getAccountId(), path.getConversationId());
}
}
} else if (requestCode == REQUEST_CODE_SCREEN_SHARE) {
if (resultCode == Activity.RESULT_OK && data != null) {
try {
startScreenShare(mProjectionManager.getMediaProjection(resultCode, data));
} catch (Exception e) {
Log.w(TAG, "Error starting screen sharing", e);
}
} else {
binding.callScreenshareBtn.setChecked(false);
}
}
}
@Override
public void onCreateOptionsMenu(@NonNull Menu m, @NonNull MenuInflater inf) {
super.onCreateOptionsMenu(m, inf);
inf.inflate(R.menu.ac_call, m);
dialPadBtn = m.findItem(R.id.menuitem_dialpad);
pluginsMenuBtn = m.findItem(R.id.menuitem_video_plugins);
}
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
super.onPrepareOptionsMenu(menu);
presenter.prepareOptionMenu();
}
@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();
}
return true;
}
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
AppCompatActivity activity = (AppCompatActivity) getActivity();
ActionBar actionBar = activity == null ? null : activity.getSupportActionBar();
if (actionBar != null) {
if (isInPictureInPictureMode) {
actionBar.hide();
} else {
mBackstackLost = true;
actionBar.show();
}
}
presenter.pipModeChanged(isInPictureInPictureMode);
}
@Override
public void displayContactBubble(final boolean display) {
if (binding != null)
binding.contactBubbleLayout.getHandler().post(() -> {
if (binding != null) binding.contactBubbleLayout.setVisibility(display ? View.VISIBLE : View.GONE);
});
}
@Override
public void displayVideoSurface(final boolean displayVideoSurface, final boolean displayPreviewContainer) {
binding.videoSurface.setVisibility(displayVideoSurface ? View.VISIBLE : View.GONE);
if (choosePluginMode) {
binding.pluginPreviewSurface.setVisibility(displayPreviewContainer ? View.VISIBLE : View.GONE);
binding.pluginPreviewContainer.setVisibility(displayPreviewContainer ? View.VISIBLE : View.GONE);
binding.previewContainer.setVisibility(View.GONE);
} else {
binding.pluginPreviewSurface.setVisibility(View.GONE);
binding.pluginPreviewContainer.setVisibility(View.GONE);
binding.previewContainer.setVisibility(displayPreviewContainer ? View.VISIBLE : View.GONE);
}
updateMenu();
}
@Override
public void displayPreviewSurface(final boolean display) {
if (display) {
binding.videoSurface.setZOrderOnTop(false);
binding.videoSurface.setZOrderMediaOverlay(false);
} else {
binding.videoSurface.setZOrderMediaOverlay(true);
binding.videoSurface.setZOrderOnTop(true);
}
}
@Override
public void displayHangupButton(boolean display) {
Log.w(TAG, "displayHangupButton " + display);
display &= !choosePluginMode;
binding.callControlGroup.setVisibility(display ? View.VISIBLE : View.GONE);
binding.callHangupBtn.setVisibility(display ? View.VISIBLE : View.GONE);
binding.confControlGroup.setVisibility((mConferenceMode && display) ? View.VISIBLE : View.GONE);
}
@Override
public void displayDialPadKeyboard() {
KeyboardVisibilityManager.showKeyboard(getActivity(), binding.dialpadEditText, InputMethodManager.SHOW_FORCED);
}
@Override
public void switchCameraIcon(boolean isFront) {
binding.callCameraFlipBtn.setImageResource(isFront ? R.drawable.baseline_camera_front_24 : R.drawable.baseline_camera_rear_24);
}
@Override
public void updateAudioState(HardwareService.AudioState state) {
binding.callSpeakerBtn.setChecked(state.getOutputType() == HardwareService.AudioOutput.SPEAKERS);
}
@Override
public void updateMenu() {
requireActivity().invalidateOptionsMenu();
}
@Override
public void updateTime(final long duration) {
if (binding != null) {
if (duration <= 0)
binding.callStatusTxt.setText(null);
else
binding.callStatusTxt.setText(String.format(Locale.getDefault(), "%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60));
}
}
@Override
@SuppressLint("RestrictedApi")
public void updateContactBubble(@NonNull final List<SipCall> 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();
boolean hasProfileName = displayName != null && !displayName.contentEquals(username);
AppCompatActivity activity = (AppCompatActivity) getActivity();
if (activity != null) {
ActionBar ab = activity.getSupportActionBar();
if (ab != null) {
if (hasProfileName) {
ab.setTitle(displayName);
ab.setSubtitle(username);
} else {
ab.setTitle(username);
ab.setSubtitle(null);
}
ab.setDisplayShowTitleEnabled(true);
}
}
if (hasProfileName) {
binding.contactBubbleNumTxt.setVisibility(View.VISIBLE);
binding.contactBubbleTxt.setText(displayName);
binding.contactBubbleNumTxt.setText(username);
} else {
binding.contactBubbleNumTxt.setVisibility(View.GONE);
binding.contactBubbleTxt.setText(username);
}
binding.contactBubble.setImageDrawable(
new AvatarDrawable.Builder()
.withContact(contacts.get(0).getContact())
.withCircleCrop(true)
.withPresence(false)
.build(getActivity())
);
if (!mConferenceMode) {
binding.confControlGroup.setVisibility(View.GONE);
} else {
binding.confControlGroup.setVisibility(View.VISIBLE);
if (confAdapter == null) {
confAdapter = new ConfParticipantAdapter((view, call) -> {
if (presenter == null)
return;
boolean maximized = presenter.isMaximized(call);
PopupMenu popup = new PopupMenu(view.getContext(), view);
popup.inflate(R.menu.conference_participant_actions);
MenuBuilder menu = (MenuBuilder) popup.getMenu();
MenuItem maxItem = menu.findItem(R.id.conv_contact_maximize);
if (maximized) {
maxItem.setTitle(R.string.action_call_minimize);
maxItem.setIcon(R.drawable.baseline_close_fullscreen_24);
} else {
maxItem.setTitle(R.string.action_call_maximize);
maxItem.setIcon(R.drawable.baseline_open_in_full_24);
}
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_contact_maximize:
presenter.maximizeParticipant(call);
break;
default:
return false;
}
return true;
});
MenuPopupHelper menuHelper = new MenuPopupHelper(view.getContext(), menu, view);
menuHelper.setForceShowIcon(true);
menuHelper.show();
});
}
confAdapter.updateFromCalls(contacts);
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);
binding.participantLabelContainer.addView(label.getRoot(), params);
}
}
}
binding.participantLabelContainer.setVisibility(info.isEmpty() ? View.GONE : View.VISIBLE);
}
@Override
public void updateCallStatus(final SipCall.CallStatus callStatus) {
binding.callStatusTxt.setText(callStateToHumanState(callStatus));
}
@Override
public void initMenu(boolean isSpeakerOn, boolean displayFlip, boolean canDial,
boolean showPluginBtn, boolean onGoingCall) {
if (binding != null) {
binding.callCameraFlipBtn.setVisibility(displayFlip ? View.VISIBLE : View.GONE);
}
if (dialPadBtn != null) {
dialPadBtn.setVisible(canDial);
}
if (pluginsMenuBtn != null) {
pluginsMenuBtn.setVisible(showPluginBtn);
}
updateMenu();
}
@Override
public void initNormalStateDisplay(final boolean audioOnly, boolean isMuted) {
Log.w(TAG, "initNormalStateDisplay");
binding.shapeRipple.stopRipple();
binding.callAcceptBtn.setVisibility(View.GONE);
binding.callRefuseBtn.setVisibility(View.GONE);
binding.callControlGroup.setVisibility(View.VISIBLE);
binding.callHangupBtn.setVisibility(View.VISIBLE);
binding.contactBubbleLayout.setVisibility(audioOnly ? View.VISIBLE : View.GONE);
binding.callMicBtn.setChecked(isMuted);
requireActivity().invalidateOptionsMenu();
}
@Override
public void initIncomingCallDisplay() {
Log.w(TAG, "initIncomingCallDisplay");
binding.callAcceptBtn.setVisibility(View.VISIBLE);
binding.callRefuseBtn.setVisibility(View.VISIBLE);
binding.callControlGroup.setVisibility(View.GONE);
binding.callHangupBtn.setVisibility(View.GONE);
binding.contactBubbleLayout.setVisibility(View.VISIBLE);
requireActivity().invalidateOptionsMenu();
}
@Override
public void initOutGoingCallDisplay() {
Log.w(TAG, "initOutGoingCallDisplay");
binding.callAcceptBtn.setVisibility(View.GONE);
binding.callRefuseBtn.setVisibility(View.VISIBLE);
binding.callControlGroup.setVisibility(View.GONE);
binding.callHangupBtn.setVisibility(View.GONE);
binding.contactBubbleLayout.setVisibility(View.VISIBLE);
requireActivity().invalidateOptionsMenu();
}
@Override
public void resetPreviewVideoSize(int previewWidth, int previewHeight, int rot) {
if (previewWidth == -1 && previewHeight == -1)
return;
mPreviewWidth = previewWidth;
mPreviewHeight = previewHeight;
boolean flip = (rot % 180) != 0;
binding.previewSurface.setAspectRatio(flip ? mPreviewHeight : mPreviewWidth, flip ? mPreviewWidth : mPreviewHeight);
}
@Override
public void resetPluginPreviewVideoSize(int previewWidth, int previewHeight, int rot) {
if (previewWidth == -1 && previewHeight == -1)
return;
mPreviewWidth = previewWidth;
mPreviewHeight = previewHeight;
boolean flip = (rot % 180) != 0;
binding.pluginPreviewSurface.setAspectRatio(flip ? mPreviewHeight : mPreviewWidth, flip ? mPreviewWidth : mPreviewHeight);
}
@Override
public void resetVideoSize(int videoWidth, int videoHeight) {
ViewGroup rootView = (ViewGroup) getView();
if (rootView == null)
return;
double videoRatio = videoWidth / (double) videoHeight;
double screenRatio = rootView.getWidth() / (double) rootView.getHeight();
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.videoSurface.getLayoutParams();
int oldW = params.width;
int oldH = params.height;
if (videoRatio >= screenRatio) {
params.width = RelativeLayout.LayoutParams.MATCH_PARENT;
params.height = (int) (videoHeight * (double) rootView.getWidth() / (double) videoWidth);
} else {
params.height = RelativeLayout.LayoutParams.MATCH_PARENT;
params.width = (int) (videoWidth * (double) rootView.getHeight() / (double) videoHeight);
}
if (oldW != params.width || oldH != params.height) {
binding.videoSurface.setLayoutParams(params);
}
mVideoWidth = videoWidth;
mVideoHeight = videoHeight;
}
private void configureTransform(int viewWidth, int viewHeight) {
Activity activity = getActivity();
if (null == binding || null == activity) {
return;
}
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
boolean rot = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation;
// Log.w(TAG, "configureTransform " + viewWidth + "x" + viewHeight + " rot=" + rot + " mPreviewWidth=" + mPreviewWidth + " mPreviewHeight=" + mPreviewHeight);
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (rot) {
RectF bufferRect = new RectF(0, 0, mPreviewHeight, mPreviewWidth);
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewHeight,
(float) viewWidth / mPreviewWidth);
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
if(!choosePluginMode) {
// binding.pluginPreviewSurface.setTransform(matrix);
// }
// else {
binding.previewSurface.setTransform(matrix);
}
}
@Override
public void goToConversation(String accountId, String conversationId) {
Context context = requireContext();
if (DeviceUtils.isTablet(context)) {
startActivity(new Intent(DRingService.ACTION_CONV_ACCEPT, ConversationPath.toUri(accountId, conversationId), context, HomeActivity.class));
} else {
startActivityForResult(new Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, conversationId), context, ConversationActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT), HomeActivity.REQUEST_CODE_CONVERSATION);
}
}
@Override
public void goToAddContact(CallContact callContact) {
startActivityForResult(ActionHelper.getAddNumberIntentForContact(callContact),
ConversationFragment.REQ_ADD_CONTACT);
}
@Override
public void goToContact(String accountId, CallContact contact) {
startActivity(new Intent(Intent.ACTION_VIEW, android.net.Uri.withAppendedPath(android.net.Uri.withAppendedPath(ContentUriHandler.CONTACT_CONTENT_URI, accountId), contact.getPrimaryNumber()))
.setClass(requireContext(), ContactDetailsActivity.class));
}
/**
* Checks if permissions are accepted for camera and microphone. Takes into account whether call is incoming and outgoing, and requests permissions if not available.
* Initializes the call if permissions are accepted.
*
* @param isIncoming true if call is incoming, false for outgoing
* @see #initializeCall(boolean) initializeCall
*/
@Override
public void prepareCall(boolean isIncoming) {
boolean audioGranted = mDeviceRuntimeService.hasAudioPermission();
boolean audioOnly;
int permissionType;
if (isIncoming) {
audioOnly = presenter.isAudioOnly();
permissionType = REQUEST_PERMISSION_INCOMING;
} else {
Bundle args = getArguments();
audioOnly = args != null && args.getBoolean(KEY_AUDIO_ONLY);
permissionType = REQUEST_PERMISSION_OUTGOING;
}
if (!audioOnly) {
boolean videoGranted = mDeviceRuntimeService.hasVideoPermission();
if ((!audioGranted || !videoGranted) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ArrayList<String> perms = new ArrayList<>();
if (!videoGranted) {
perms.add(Manifest.permission.CAMERA);
}
if (!audioGranted) {
perms.add(Manifest.permission.RECORD_AUDIO);
}
requestPermissions(perms.toArray(new String[perms.size()]), permissionType);
} else if (audioGranted && videoGranted) {
initializeCall(isIncoming);
}
} else {
if (!audioGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, permissionType);
} else if (audioGranted) {
initializeCall(isIncoming);
}
}
}
/**
* Starts a call. Takes into account whether call is incoming or outgoing.
*
* @param isIncoming true if call is incoming, false for outgoing
*/
private void initializeCall(boolean isIncoming) {
if (isIncoming) {
presenter.acceptCall();
} else {
Bundle args;
args = getArguments();
if (args != null) {
presenter.initOutGoing(getArguments().getString(KEY_ACCOUNT_ID),
getArguments().getString(ConversationFragment.KEY_CONTACT_RING_ID),
getArguments().getBoolean(KEY_AUDIO_ONLY));
}
}
}
@Override
public void finish() {
Activity activity = getActivity();
if (activity != null) {
activity.finishAndRemoveTask();
if (mBackstackLost) {
startActivity(Intent.makeMainActivity(new ComponentName(activity, HomeActivity.class)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
}
}
public void speakerClicked() {
presenter.speakerClick(binding.callSpeakerBtn.isChecked());
}
private void startScreenShare(MediaProjection mediaProjection) {
if (presenter.startScreenShare(mediaProjection)) {
if(choosePluginMode) {
binding.pluginPreviewSurface.setVisibility(View.GONE);
} else {
binding.previewSurface.setVisibility(View.GONE);
}
} else {
Toast.makeText(requireContext(), "Can't start screen sharing", Toast.LENGTH_SHORT).show();
}
}
private void stopShareScreen() {
binding.previewSurface.setVisibility(View.VISIBLE);
presenter.stopScreenShare();
}
public void shareScreenClicked(boolean checked) {
if (!checked) {
stopShareScreen();
} else {
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_SHARE);
}
}
public void micClicked() {
presenter.muteMicrophoneToggled(binding.callMicBtn.isChecked());
}
public void hangUpClicked() {
presenter.hangupCall();
}
public void refuseClicked() {
presenter.refuseCall();
}
public void acceptClicked() {
prepareCall(true);
}
public void cameraFlip() {
presenter.switchVideoInputClick();
}
public void addParticipant() {
presenter.startAddParticipant();
}
@Override
public void startAddParticipant(String conferenceId) {
startActivityForResult(
new Intent(Intent.ACTION_PICK)
.setClass(requireActivity(), ConversationSelectionActivity.class)
.putExtra(KEY_CONF_ID, conferenceId),
CallFragment.REQUEST_CODE_ADD_PARTICIPANT);
}
@Override
public void toggleCallMediaHandler(String id, String callId, boolean toggle) {
Ringservice.toggleCallMediaHandler(id, callId, toggle);
}
public Map<String, String> getCallMediaHandlerDetails(String id) {
return Ringservice.getCallMediaHandlerDetails(id).toNative();
}
@Override
public void positiveMediaButtonClicked() {
presenter.positiveButtonClicked();
}
@Override
public void negativeMediaButtonClicked() {
presenter.negativeButtonClicked();
}
@Override
public void toggleMediaButtonClicked() {
presenter.toggleButtonClicked();
}
public boolean displayPluginsButton() {
return getPluginsEnabled();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Reset the padding of the RecyclerPicker on each
rp.setFirstLastElementsWidths(112, 112);
binding.recyclerPicker.setVisibility(View.GONE);
if (choosePluginMode) {
displayHangupButton(false);
binding.recyclerPicker.setVisibility(View.VISIBLE);
movePreview(true);
if (previousPluginPosition != -1) {
rp.scrollToPosition(previousPluginPosition);
}
} else {
movePreview(false);
displayHangupButton(true);
}
}
public void toggleVideoPluginsCarousel(boolean toggle) {
if (choosePluginMode) {
if (toggle) {
binding.recyclerPicker.setVisibility(View.VISIBLE);
movePreview(true);
} else {
binding.recyclerPicker.setVisibility(View.INVISIBLE);
movePreview(false);
}
}
}
public void movePreview(boolean up) {
// Move the preview container (cardview) by a certain margin
if(up) {
animation.setIntValues(12, 128);
} else {
animation.setIntValues(128, 12);
}
animation.start();
}
/**
* Function that is called to show/hide the plugins recycler viewer and update UI
*/
public void displayVideoPluginsCarousel() {
choosePluginMode = !choosePluginMode;
Context context = requireActivity();
// Create callMediaHandlers and videoPluginsItems in a lazy manner
if (pluginsModeFirst) {
// Init
callMediaHandlers = Ringservice.listCallMediaHandlers();
List<Drawable> videoPluginsItems = new ArrayList<>(callMediaHandlers.size() + 1);
videoPluginsItems.add(context.getDrawable(R.drawable.baseline_cancel_24));
// Search for plugin call media handlers icons
// If a call media handler doesn't have an icon use a standard android icon
for (String callMediaHandler : callMediaHandlers) {
Map<String, String> details = getCallMediaHandlerDetails(callMediaHandler);
String drawablePath = details.get("iconPath");
Drawable handlerIcon = StringUtils.isEmpty(drawablePath) ? null : Drawable.createFromPath(details.get("iconPath"));
if (handlerIcon == null) {
handlerIcon = context.getDrawable(R.drawable.ic_jami);
}
videoPluginsItems.add(handlerIcon);
}
rp.updateData(videoPluginsItems);
pluginsModeFirst = false;
}
if (choosePluginMode) {
// hide hang up button and other call buttons
displayHangupButton(false);
// Display the plugins recyclerpicker
binding.recyclerPicker.setVisibility(View.VISIBLE);
movePreview(true);
// Start loading the first or previous plugin if one was active
if(callMediaHandlers.size() > 0) {
// If no previous plugin was active, take the first, else previous
int position;
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);
}
}
} else {
if (previousPluginPosition > 0) {
String callMediaId = callMediaHandlers.
get(previousPluginPosition-1);
presenter.toggleCallMediaHandler(callMediaId, false);
rp.scrollToPosition(previousPluginPosition);
}
presenter.stopPlugin();
binding.recyclerPicker.setVisibility(View.GONE);
movePreview(false);
displayHangupButton(true);
}
//change preview image
displayVideoSurface(true,true);
}
/**
* 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 previousPluginPosition = -1 or 0, there was no plugin
*/
if (previousPluginPosition > 0) {
String callMediaId = callMediaHandlers.get(previousPluginPosition-1);
presenter.toggleCallMediaHandler(callMediaId, false);
}
if (position > 0) {
previousPluginPosition = position;
String callMediaId = callMediaHandlers.get(position-1);
presenter.toggleCallMediaHandler(callMediaId, true);
}
}
/**
* 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 previousPluginPosition = -1 or 0, there was no plugin
*/
if (previousPluginPosition > 0) {
String callMediaId = callMediaHandlers.get(previousPluginPosition-1);
presenter.toggleCallMediaHandler(callMediaId, false);
rp.scrollToPosition(previousPluginPosition);
}
CallActivity callActivity = (CallActivity) getActivity();
callActivity.showSystemUI();
toggleVideoPluginsCarousel(false);
displayVideoPluginsCarousel();
}
}
}