/* * Copyright (c) 2010-2019 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). * * 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, see . */ package org.linphone.call; import android.Manifest; import android.app.Dialog; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.CountDownTimer; import android.os.SystemClock; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.TextureView; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.Chronometer; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.drawerlayout.widget.DrawerLayout; import java.lang.ref.WeakReference; import java.util.ArrayList; import org.linphone.LinphoneContext; import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.activities.LinphoneGenericActivity; import org.linphone.chat.ChatActivity; import org.linphone.compatibility.Compatibility; import org.linphone.contacts.ContactsManager; import org.linphone.contacts.ContactsUpdatedListener; import org.linphone.contacts.LinphoneContact; import org.linphone.contacts.views.ContactAvatar; import org.linphone.core.Address; import org.linphone.core.Call; import org.linphone.core.ChatMessage; import org.linphone.core.ChatRoom; import org.linphone.core.Core; import org.linphone.core.CoreListener; import org.linphone.core.CoreListenerStub; import org.linphone.core.tools.Log; import org.linphone.dialer.DialerActivity; import org.linphone.service.LinphoneService; import org.linphone.settings.LinphonePreferences; import org.linphone.utils.LinphoneUtils; public class CallActivity extends LinphoneGenericActivity implements CallStatusBarFragment.StatsClikedListener, ContactsUpdatedListener, CallActivityInterface { private static final int SECONDS_BEFORE_HIDING_CONTROLS = 4000; private static final int SECONDS_BEFORE_DENYING_CALL_UPDATE = 30000; private static final int CAMERA_TO_TOGGLE_VIDEO = 0; private static final int MIC_TO_DISABLE_MUTE = 1; private static final int WRITE_EXTERNAL_STORAGE_FOR_RECORDING = 2; private static final int CAMERA_TO_ACCEPT_UPDATE = 3; private static final int ALL_PERMISSIONS = 4; private static class HideControlsRunnable implements Runnable { private WeakReference mWeakCallActivity; public HideControlsRunnable(CallActivity activity) { mWeakCallActivity = new WeakReference<>(activity); } @Override public void run() { // Make sure that at the time this is executed this is still required if (!LinphoneContext.isReady()) return; Call call = LinphoneManager.getCore().getCurrentCall(); if (call != null && call.getCurrentParams().videoEnabled()) { CallActivity activity = mWeakCallActivity.get(); if (activity != null) activity.updateButtonsVisibility(false); } } } private final HideControlsRunnable mHideControlsRunnable = new HideControlsRunnable(this); private float mPreviewX, mPreviewY; private TextureView mLocalPreview, mRemoteVideo; private RelativeLayout mButtons, mActiveCalls, mContactAvatar, mActiveCallHeader, mConferenceHeader; private LinearLayout mCallsList, mCallPausedByRemote, mConferenceList; private ImageView mMicro, mSpeaker, mVideo; private ImageView mPause, mSwitchCamera, mRecordingInProgress; private ImageView mExtrasButtons, mAddCall, mTransferCall, mRecordCall, mConference; private ImageView mAudioRoute, mRouteEarpiece, mRouteSpeaker, mRouteBluetooth; private TextView mContactName, mMissedMessages; private ProgressBar mVideoInviteInProgress; private Chronometer mCallTimer; private CountDownTimer mCallUpdateCountDownTimer; private Dialog mCallUpdateDialog; private CallStatsFragment mStatsFragment; private Core mCore; private CoreListener mListener; private AndroidAudioManager mAudioManager; private VideoZoomHelper mZoomHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Compatibility.setShowWhenLocked(this, true); setContentView(R.layout.call); mLocalPreview = findViewById(R.id.local_preview_texture); mLocalPreview.setOnTouchListener( new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return moveLocalPreview(v, event); } }); mRemoteVideo = findViewById(R.id.remote_video_texture); mRemoteVideo.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { makeButtonsVisibleTemporary(); } }); mActiveCalls = findViewById(R.id.active_calls); mActiveCallHeader = findViewById(R.id.active_call); mCallPausedByRemote = findViewById(R.id.remote_pause); mCallsList = findViewById(R.id.calls_list); mConferenceList = findViewById(R.id.conference_list); mConferenceHeader = findViewById(R.id.conference_header); mButtons = findViewById(R.id.buttons); ImageView conferencePause = findViewById(R.id.conference_pause); conferencePause.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { LinphoneManager.getCallManager().pauseConference(); updateCallsList(); } }); mContactName = findViewById(R.id.current_contact_name); mContactAvatar = findViewById(R.id.avatar_layout); mCallTimer = findViewById(R.id.current_call_timer); mVideoInviteInProgress = findViewById(R.id.video_in_progress); mVideo = findViewById(R.id.video); mVideo.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { if (checkAndRequestPermission( Manifest.permission.CAMERA, CAMERA_TO_TOGGLE_VIDEO)) { toggleVideo(); } } }); mMicro = findViewById(R.id.micro); mMicro.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { if (checkAndRequestPermission( Manifest.permission.RECORD_AUDIO, MIC_TO_DISABLE_MUTE)) { toggleMic(); } } }); mSpeaker = findViewById(R.id.speaker); mSpeaker.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { toggleSpeaker(); } }); mAudioRoute = findViewById(R.id.audio_route); mAudioRoute.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { toggleAudioRouteButtons(); } }); mRouteEarpiece = findViewById(R.id.route_earpiece); mRouteEarpiece.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { mAudioManager.routeAudioToEarPiece(); updateAudioRouteButtons(); } }); mRouteSpeaker = findViewById(R.id.route_speaker); mRouteSpeaker.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { mAudioManager.routeAudioToSpeaker(); updateAudioRouteButtons(); } }); mRouteBluetooth = findViewById(R.id.route_bluetooth); mRouteBluetooth.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { mAudioManager.routeAudioToBluetooth(); updateAudioRouteButtons(); } }); mExtrasButtons = findViewById(R.id.options); mExtrasButtons.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { toggleExtrasButtons(); } }); mExtrasButtons.setSelected(false); mExtrasButtons.setEnabled(!getResources().getBoolean(R.bool.disable_options_in_call)); mAddCall = findViewById(R.id.add_call); mAddCall.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { goBackToDialer(); } }); mTransferCall = findViewById(R.id.transfer); mTransferCall.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { goBackToDialerAndDisplayTransferButton(); } }); mTransferCall.setEnabled(getResources().getBoolean(R.bool.allow_transfers)); mConference = findViewById(R.id.conference); mConference.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { mCore.addAllToConference(); } }); mRecordCall = findViewById(R.id.record_call); mRecordCall.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { if (checkAndRequestPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE_FOR_RECORDING)) { toggleRecording(); } } }); ImageView numpadButton = findViewById(R.id.dialer); numpadButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { View numpad = findViewById(R.id.numpad); boolean isNumpadVisible = numpad.getVisibility() == View.VISIBLE; numpad.setVisibility(isNumpadVisible ? View.GONE : View.VISIBLE); v.setSelected(!isNumpadVisible); } }); numpadButton.setSelected(false); ImageView hangUp = findViewById(R.id.hang_up); hangUp.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { LinphoneManager.getCallManager().terminateCurrentCallOrConferenceOrAll(); } }); ImageView chat = findViewById(R.id.chat); chat.setEnabled(!getResources().getBoolean(R.bool.disable_chat)); chat.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { goToChatList(); } }); mPause = findViewById(R.id.pause); mPause.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { togglePause(mCore.getCurrentCall()); } }); mSwitchCamera = findViewById(R.id.switchCamera); mSwitchCamera.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { LinphoneManager.getCallManager().switchCamera(); } }); mRecordingInProgress = findViewById(R.id.recording); mRecordingInProgress.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { toggleRecording(); } }); mMissedMessages = findViewById(R.id.missed_chats); DrawerLayout sideMenu = findViewById(R.id.side_menu); RelativeLayout sideMenuContent = findViewById(R.id.side_menu_content); mStatsFragment = (CallStatsFragment) getFragmentManager().findFragmentById(R.id.call_stats_fragment); mStatsFragment.setDrawer(sideMenu, sideMenuContent); CallStatusBarFragment statusBarFragment = (CallStatusBarFragment) getFragmentManager().findFragmentById(R.id.status_bar_fragment); statusBarFragment.setStatsListener(this); mZoomHelper = new VideoZoomHelper(this, mRemoteVideo); mListener = new CoreListenerStub() { @Override public void onMessageReceived(Core core, ChatRoom cr, ChatMessage message) { updateMissedChatCount(); } @Override public void onCallStateChanged( Core core, Call call, Call.State state, String message) { if (state == Call.State.End || state == Call.State.Released) { if (core.getCallsNb() == 0) { finish(); } else { showVideoControls(false); } } else if (state == Call.State.PausedByRemote) { if (core.getCurrentCall() != null) { showVideoControls(false); mCallPausedByRemote.setVisibility(View.VISIBLE); } } else if (state == Call.State.Pausing || state == Call.State.Paused) { if (core.getCurrentCall() != null) { showVideoControls(false); } } else if (state == Call.State.StreamsRunning) { mCallPausedByRemote.setVisibility(View.GONE); setCurrentCallContactInformation(); updateInterfaceDependingOnVideo(); } else if (state == Call.State.UpdatedByRemote) { // If the correspondent asks for video while in audio call boolean videoEnabled = LinphonePreferences.instance().isVideoEnabled(); if (!videoEnabled) { // Video is disabled globally, don't even ask user acceptCallUpdate(false); return; } boolean showAcceptUpdateDialog = LinphoneManager.getCallManager() .shouldShowAcceptCallUpdateDialog(call); if (showAcceptUpdateDialog) { showAcceptCallUpdateDialog(); createTimerForDialog(SECONDS_BEFORE_DENYING_CALL_UPDATE); } } updateButtons(); updateCallsList(); } }; mCore = LinphoneManager.getCore(); if (mCore != null) { boolean recordAudioPermissionGranted = checkPermission(Manifest.permission.RECORD_AUDIO); if (!recordAudioPermissionGranted) { Log.w("[Call Activity] RECORD_AUDIO permission denied, muting microphone"); mCore.enableMic(false); } Call call = mCore.getCurrentCall(); boolean videoEnabled = LinphonePreferences.instance().isVideoEnabled() && call != null && call.getCurrentParams().videoEnabled(); if (videoEnabled) { mAudioManager = LinphoneManager.getAudioManager(); mAudioManager.routeAudioToSpeaker(); mSpeaker.setSelected(true); } } } @Override protected void onStart() { super.onStart(); // This also must be done here in case of an outgoing call accepted // before user granted or denied permissions // or if an incoming call was answer from the notification checkAndRequestCallPermissions(); mCore = LinphoneManager.getCore(); if (mCore != null) { mCore.setNativeVideoWindowId(mRemoteVideo); mCore.setNativePreviewWindowId(mLocalPreview); mCore.addListener(mListener); } } @Override protected void onResume() { super.onResume(); mAudioManager = LinphoneManager.getAudioManager(); updateButtons(); updateMissedChatCount(); updateInterfaceDependingOnVideo(); updateCallsList(); ContactsManager.getInstance().addContactsListener(this); LinphoneManager.getCallManager().setCallInterface(this); if (mCore.getCallsNb() == 0) { Log.w("[Call Activity] Resuming but no call found..."); finish(); } if (LinphoneService.isReady()) LinphoneService.instance().destroyOverlay(); } @Override protected void onPause() { ContactsManager.getInstance().removeContactsListener(this); LinphoneManager.getCallManager().setCallInterface(null); Core core = LinphoneManager.getCore(); if (LinphonePreferences.instance().isOverlayEnabled() && core != null && core.getCurrentCall() != null) { Call call = core.getCurrentCall(); if (call.getState() == Call.State.StreamsRunning) { // Prevent overlay creation if video call is paused by remote if (LinphoneService.isReady()) LinphoneService.instance().createOverlay(); } } super.onPause(); } @Override protected void onDestroy() { Core core = LinphoneManager.getCore(); if (core != null) { core.removeListener(mListener); core.setNativeVideoWindowId(null); core.setNativePreviewWindowId(null); } if (mZoomHelper != null) { mZoomHelper.destroy(); mZoomHelper = null; } if (mCallUpdateCountDownTimer != null) { mCallUpdateCountDownTimer.cancel(); mCallUpdateCountDownTimer = null; } mCallTimer.stop(); mCallTimer = null; mListener = null; mLocalPreview = null; mRemoteVideo = null; mStatsFragment = null; mButtons = null; mActiveCalls = null; mContactAvatar = null; mActiveCallHeader = null; mConferenceHeader = null; mCallsList = null; mCallPausedByRemote = null; mConferenceList = null; mMicro = null; mSpeaker = null; mVideo = null; mPause = null; mSwitchCamera = null; mRecordingInProgress = null; mExtrasButtons = null; mAddCall = null; mTransferCall = null; mRecordCall = null; mConference = null; mAudioRoute = null; mRouteEarpiece = null; mRouteSpeaker = null; mRouteBluetooth = null; mContactName = null; mMissedMessages = null; mVideoInviteInProgress = null; mCallUpdateDialog = null; super.onDestroy(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mAudioManager.onKeyVolumeAdjust(keyCode)) return true; return super.onKeyDown(keyCode, event); } @Override public void onStatsClicked() { if (mStatsFragment.isOpened()) { mStatsFragment.openOrCloseSideMenu(false, true); } else { mStatsFragment.openOrCloseSideMenu(true, true); } } @Override public void onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { // Permission not granted, won't change anything if (requestCode == ALL_PERMISSIONS) { for (int index = 0; index < permissions.length; index++) { int granted = grantResults[index]; if (granted == PackageManager.PERMISSION_GRANTED) { String permission = permissions[index]; if (Manifest.permission.RECORD_AUDIO.equals(permission)) { toggleMic(); } else if (Manifest.permission.CAMERA.equals(permission)) { LinphoneUtils.reloadVideoDevices(); } } } } else { if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return; switch (requestCode) { case CAMERA_TO_TOGGLE_VIDEO: LinphoneUtils.reloadVideoDevices(); toggleVideo(); break; case MIC_TO_DISABLE_MUTE: toggleMic(); break; case WRITE_EXTERNAL_STORAGE_FOR_RECORDING: toggleRecording(); break; case CAMERA_TO_ACCEPT_UPDATE: LinphoneUtils.reloadVideoDevices(); acceptCallUpdate(true); break; } } } private boolean checkPermission(String permission) { int granted = getPackageManager().checkPermission(permission, getPackageName()); Log.i( "[Permission] " + permission + " permission is " + (granted == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); return granted == PackageManager.PERMISSION_GRANTED; } private boolean checkAndRequestPermission(String permission, int result) { if (!checkPermission(permission)) { Log.i("[Permission] Asking for " + permission); ActivityCompat.requestPermissions(this, new String[] {permission}, result); return false; } return true; } private void checkAndRequestCallPermissions() { ArrayList permissionsList = new ArrayList<>(); int recordAudio = getPackageManager() .checkPermission(Manifest.permission.RECORD_AUDIO, getPackageName()); Log.i( "[Permission] Record audio permission is " + (recordAudio == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); int camera = getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName()); Log.i( "[Permission] Camera permission is " + (camera == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); int readPhoneState = getPackageManager() .checkPermission(Manifest.permission.READ_PHONE_STATE, getPackageName()); Log.i( "[Permission] Read phone state permission is " + (camera == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); if (recordAudio != PackageManager.PERMISSION_GRANTED) { Log.i("[Permission] Asking for record audio"); permissionsList.add(Manifest.permission.RECORD_AUDIO); } if (readPhoneState != PackageManager.PERMISSION_GRANTED) { Log.i("[Permission] Asking for read phone state"); permissionsList.add(Manifest.permission.READ_PHONE_STATE); } Call call = mCore.getCurrentCall(); if (LinphonePreferences.instance().shouldInitiateVideoCall() || (LinphonePreferences.instance().shouldAutomaticallyAcceptVideoRequests() && call != null && call.getRemoteParams().videoEnabled())) { if (camera != PackageManager.PERMISSION_GRANTED) { Log.i("[Permission] Asking for camera"); permissionsList.add(Manifest.permission.CAMERA); } } if (permissionsList.size() > 0) { String[] permissions = new String[permissionsList.size()]; permissions = permissionsList.toArray(permissions); ActivityCompat.requestPermissions(this, permissions, ALL_PERMISSIONS); } } @Override public void onContactsUpdated() { setCurrentCallContactInformation(); } @Override public void onUserLeaveHint() { if (mCore == null) return; Call call = mCore.getCurrentCall(); if (call == null) return; boolean videoEnabled = LinphonePreferences.instance().isVideoEnabled() && call.getCurrentParams().videoEnabled(); if (videoEnabled && getResources().getBoolean(R.bool.allow_pip_while_video_call)) { Compatibility.enterPipMode(this); } } @Override public void onPictureInPictureModeChanged( boolean isInPictureInPictureMode, Configuration newConfig) { if (isInPictureInPictureMode) { updateButtonsVisibility(false); } } @Override public void refreshInCallActions() { updateButtons(); } @Override public void resetCallControlsHidingTimer() { LinphoneUtils.removeFromUIThreadDispatcher(mHideControlsRunnable); LinphoneUtils.dispatchOnUIThreadAfter( mHideControlsRunnable, SECONDS_BEFORE_HIDING_CONTROLS); } // BUTTONS private void updateAudioRouteButtons() { mRouteSpeaker.setSelected(mAudioManager.isAudioRoutedToSpeaker()); mRouteBluetooth.setSelected(mAudioManager.isUsingBluetoothAudioRoute()); mRouteEarpiece.setSelected(mAudioManager.isAudioRoutedToEarpiece()); } private void updateButtons() { Call call = mCore.getCurrentCall(); mMicro.setSelected(!mCore.micEnabled()); mSpeaker.setSelected(mAudioManager.isAudioRoutedToSpeaker()); updateAudioRouteButtons(); boolean isBluetoothAvailable = mAudioManager.isBluetoothHeadsetConnected(); mSpeaker.setVisibility(isBluetoothAvailable ? View.GONE : View.VISIBLE); mAudioRoute.setVisibility(isBluetoothAvailable ? View.VISIBLE : View.GONE); if (!isBluetoothAvailable) { mRouteBluetooth.setVisibility(View.GONE); mRouteSpeaker.setVisibility(View.GONE); mRouteEarpiece.setVisibility(View.GONE); } mVideo.setEnabled( LinphonePreferences.instance().isVideoEnabled() && call != null && !call.mediaInProgress()); mVideo.setSelected(call != null && call.getCurrentParams().videoEnabled()); mSwitchCamera.setVisibility( call != null && call.getCurrentParams().videoEnabled() ? View.VISIBLE : View.INVISIBLE); mPause.setEnabled(call != null && !call.mediaInProgress()); mRecordCall.setSelected(call != null && call.isRecording()); mRecordingInProgress.setVisibility( call != null && call.isRecording() ? View.VISIBLE : View.GONE); mConference.setEnabled( mCore.getCallsNb() > 1 && mCore.getCallsNb() > mCore.getConferenceSize() && !mCore.soundResourcesLocked()); } private void toggleMic() { mCore.enableMic(!mCore.micEnabled()); mMicro.setSelected(!mCore.micEnabled()); } private void toggleSpeaker() { if (mAudioManager.isAudioRoutedToSpeaker()) { mAudioManager.routeAudioToEarPiece(); } else { mAudioManager.routeAudioToSpeaker(); } mSpeaker.setSelected(mAudioManager.isAudioRoutedToSpeaker()); } private void toggleVideo() { Call call = mCore.getCurrentCall(); if (call == null) return; mVideoInviteInProgress.setVisibility(View.VISIBLE); mVideo.setEnabled(false); if (call.getCurrentParams().videoEnabled()) { LinphoneManager.getCallManager().removeVideo(); } else { LinphoneManager.getCallManager().addVideo(); } } private void togglePause(Call call) { if (call == null) return; if (call == mCore.getCurrentCall()) { call.pause(); mPause.setSelected(true); } else if (call.getState() == Call.State.Paused) { call.resume(); mPause.setSelected(false); } } private void toggleAudioRouteButtons() { mAudioRoute.setSelected(!mAudioRoute.isSelected()); mRouteEarpiece.setVisibility(mAudioRoute.isSelected() ? View.VISIBLE : View.GONE); mRouteSpeaker.setVisibility(mAudioRoute.isSelected() ? View.VISIBLE : View.GONE); mRouteBluetooth.setVisibility(mAudioRoute.isSelected() ? View.VISIBLE : View.GONE); } private void toggleExtrasButtons() { mExtrasButtons.setSelected(!mExtrasButtons.isSelected()); mAddCall.setVisibility(mExtrasButtons.isSelected() ? View.VISIBLE : View.GONE); mTransferCall.setVisibility(mExtrasButtons.isSelected() ? View.VISIBLE : View.GONE); mRecordCall.setVisibility(mExtrasButtons.isSelected() ? View.VISIBLE : View.GONE); mConference.setVisibility(mExtrasButtons.isSelected() ? View.VISIBLE : View.GONE); } private void toggleRecording() { Call call = mCore.getCurrentCall(); if (call == null) return; if (call.isRecording()) { call.stopRecording(); } else { call.startRecording(); } mRecordCall.setSelected(call.isRecording()); mRecordingInProgress.setVisibility(call.isRecording() ? View.VISIBLE : View.INVISIBLE); } private void updateMissedChatCount() { int count = 0; if (mCore != null) { count = mCore.getUnreadChatMessageCountFromActiveLocals(); } if (count > 0) { mMissedMessages.setText(String.valueOf(count)); mMissedMessages.setVisibility(View.VISIBLE); } else { mMissedMessages.clearAnimation(); mMissedMessages.setVisibility(View.GONE); } } private void updateButtonsVisibility(boolean visible) { findViewById(R.id.status_bar_fragment).setVisibility(visible ? View.VISIBLE : View.GONE); if (mActiveCalls != null) mActiveCalls.setVisibility(visible ? View.VISIBLE : View.GONE); if (mButtons != null) mButtons.setVisibility(visible ? View.VISIBLE : View.GONE); } private void makeButtonsVisibleTemporary() { updateButtonsVisibility(true); resetCallControlsHidingTimer(); } // VIDEO RELATED private void showVideoControls(boolean videoEnabled) { mContactAvatar.setVisibility(videoEnabled ? View.GONE : View.VISIBLE); mRemoteVideo.setVisibility(videoEnabled ? View.VISIBLE : View.GONE); mLocalPreview.setVisibility(videoEnabled ? View.VISIBLE : View.GONE); mSwitchCamera.setVisibility(videoEnabled ? View.VISIBLE : View.INVISIBLE); updateButtonsVisibility(!videoEnabled); mVideo.setSelected(videoEnabled); LinphoneManager.getInstance().enableProximitySensing(!videoEnabled); if (!videoEnabled) { LinphoneUtils.removeFromUIThreadDispatcher(mHideControlsRunnable); } } private void updateInterfaceDependingOnVideo() { Call call = mCore.getCurrentCall(); if (call == null) { showVideoControls(false); return; } mVideoInviteInProgress.setVisibility(View.GONE); mVideo.setEnabled( LinphonePreferences.instance().isVideoEnabled() && call != null && !call.mediaInProgress()); boolean videoEnabled = LinphonePreferences.instance().isVideoEnabled() && call != null && call.getCurrentParams().videoEnabled(); showVideoControls(videoEnabled); } private boolean moveLocalPreview(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: mPreviewX = view.getX() - motionEvent.getRawX(); mPreviewY = view.getY() - motionEvent.getRawY(); break; case MotionEvent.ACTION_MOVE: view.animate() .x(motionEvent.getRawX() + mPreviewX) .y(motionEvent.getRawY() + mPreviewY) .setDuration(0) .start(); break; default: return false; } return true; } // NAVIGATION private void goBackToDialer() { Intent intent = new Intent(); intent.setClass(this, DialerActivity.class); intent.putExtra("isTransfer", false); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); } private void goBackToDialerAndDisplayTransferButton() { Intent intent = new Intent(); intent.setClass(this, DialerActivity.class); intent.putExtra("isTransfer", true); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); } private void goToChatList() { Intent intent = new Intent(); intent.setClass(this, ChatActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); } // CALL UPDATE private void createTimerForDialog(long time) { mCallUpdateCountDownTimer = new CountDownTimer(time, 1000) { public void onTick(long millisUntilFinished) {} public void onFinish() { if (mCallUpdateDialog != null) { mCallUpdateDialog.dismiss(); mCallUpdateDialog = null; } acceptCallUpdate(false); } }.start(); } private void acceptCallUpdate(boolean accept) { if (mCallUpdateCountDownTimer != null) { mCallUpdateCountDownTimer.cancel(); } LinphoneManager.getCallManager().acceptCallUpdate(accept); } private void showAcceptCallUpdateDialog() { mCallUpdateDialog = new Dialog(this); mCallUpdateDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); mCallUpdateDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); mCallUpdateDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); mCallUpdateDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Drawable d = new ColorDrawable(ContextCompat.getColor(this, R.color.dark_grey_color)); d.setAlpha(200); mCallUpdateDialog.setContentView(R.layout.dialog); mCallUpdateDialog .getWindow() .setLayout( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); mCallUpdateDialog.getWindow().setBackgroundDrawable(d); TextView customText = mCallUpdateDialog.findViewById(R.id.dialog_message); customText.setText(getResources().getString(R.string.add_video_dialog)); mCallUpdateDialog.findViewById(R.id.dialog_delete_button).setVisibility(View.GONE); Button accept = mCallUpdateDialog.findViewById(R.id.dialog_ok_button); accept.setVisibility(View.VISIBLE); accept.setText(R.string.accept); Button cancel = mCallUpdateDialog.findViewById(R.id.dialog_cancel_button); cancel.setText(R.string.decline); accept.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { if (checkPermission(Manifest.permission.CAMERA)) { acceptCallUpdate(true); } else { checkAndRequestPermission( Manifest.permission.CAMERA, CAMERA_TO_ACCEPT_UPDATE); } mCallUpdateDialog.dismiss(); mCallUpdateDialog = null; } }); cancel.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { acceptCallUpdate(false); mCallUpdateDialog.dismiss(); mCallUpdateDialog = null; } }); mCallUpdateDialog.show(); } // CONFERENCE private void displayConferenceCall(final Call call) { LinearLayout conferenceCallView = (LinearLayout) LayoutInflater.from(this) .inflate(R.layout.call_conference_cell, null, false); TextView contactNameView = conferenceCallView.findViewById(R.id.contact_name); LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(call.getRemoteAddress()); if (contact != null) { ContactAvatar.displayAvatar( contact, conferenceCallView.findViewById(R.id.avatar_layout), true); contactNameView.setText(contact.getFullName()); } else { String displayName = LinphoneUtils.getAddressDisplayName(call.getRemoteAddress()); ContactAvatar.displayAvatar( displayName, conferenceCallView.findViewById(R.id.avatar_layout), true); contactNameView.setText(displayName); } Chronometer timer = conferenceCallView.findViewById(R.id.call_timer); timer.setBase(SystemClock.elapsedRealtime() - 1000 * call.getDuration()); timer.start(); ImageView removeFromConference = conferenceCallView.findViewById(R.id.remove_from_conference); removeFromConference.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { LinphoneManager.getCallManager().removeCallFromConference(call); } }); mConferenceList.addView(conferenceCallView); } private void displayPausedConference() { LinearLayout pausedConferenceView = (LinearLayout) LayoutInflater.from(this) .inflate(R.layout.call_conference_paused_cell, null, false); ImageView conferenceResume = pausedConferenceView.findViewById(R.id.conference_resume); conferenceResume.setSelected(true); conferenceResume.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { LinphoneManager.getCallManager().resumeConference(); updateCallsList(); } }); mCallsList.addView(pausedConferenceView); } // OTHER private void updateCallsList() { Call currentCall = mCore.getCurrentCall(); if (currentCall != null) { setCurrentCallContactInformation(); } boolean callThatIsNotCurrentFound = false; boolean pausedConferenceDisplayed = false; boolean conferenceDisplayed = false; mCallsList.removeAllViews(); mConferenceList.removeAllViews(); for (Call call : mCore.getCalls()) { if (call != null && call.getConference() != null) { if (mCore.isInConference()) { displayConferenceCall(call); conferenceDisplayed = true; } else if (!pausedConferenceDisplayed) { displayPausedConference(); pausedConferenceDisplayed = true; } } else if (call != null && call != currentCall) { Call.State state = call.getState(); if (state == Call.State.Paused || state == Call.State.PausedByRemote || state == Call.State.Pausing) { displayPausedCall(call); callThatIsNotCurrentFound = true; } } } mCallsList.setVisibility( pausedConferenceDisplayed || callThatIsNotCurrentFound ? View.VISIBLE : View.GONE); mActiveCallHeader.setVisibility( currentCall != null && !conferenceDisplayed ? View.VISIBLE : View.GONE); mConferenceHeader.setVisibility(conferenceDisplayed ? View.VISIBLE : View.GONE); mConferenceList.setVisibility(mConferenceHeader.getVisibility()); } private void displayPausedCall(final Call call) { LinearLayout callView = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.call_inactive_row, null, false); TextView contactName = callView.findViewById(R.id.contact_name); Address address = call.getRemoteAddress(); LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(address); if (contact == null) { String displayName = LinphoneUtils.getAddressDisplayName(address); contactName.setText(displayName); ContactAvatar.displayAvatar(displayName, callView.findViewById(R.id.avatar_layout)); } else { contactName.setText(contact.getFullName()); ContactAvatar.displayAvatar(contact, callView.findViewById(R.id.avatar_layout)); } Chronometer timer = callView.findViewById(R.id.call_timer); timer.setBase(SystemClock.elapsedRealtime() - 1000 * call.getDuration()); timer.start(); ImageView resumeCall = callView.findViewById(R.id.call_pause); resumeCall.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { togglePause(call); } }); mCallsList.addView(callView); } private void updateCurrentCallTimer() { Call call = mCore.getCurrentCall(); if (call == null) return; mCallTimer.setBase(SystemClock.elapsedRealtime() - 1000 * call.getDuration()); mCallTimer.start(); } private void setCurrentCallContactInformation() { updateCurrentCallTimer(); Call call = mCore.getCurrentCall(); if (call == null) return; LinphoneContact contact = ContactsManager.getInstance().findContactFromAddress(call.getRemoteAddress()); if (contact != null) { ContactAvatar.displayAvatar(contact, mContactAvatar, true); mContactName.setText(contact.getFullName()); } else { String displayName = LinphoneUtils.getAddressDisplayName(call.getRemoteAddress()); ContactAvatar.displayAvatar(displayName, mContactAvatar, true); mContactName.setText(displayName); } } }